From bb7d8ca3c5a787500ff37c399f9853f8b8ecd806 Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 11 Mar 2016 19:05:19 +0100 Subject: [PATCH 01/31] added learning as new engine selection in options Former-commit-id: e00c7ad75d19b93437d998291cc48147ad694564 --- src/cli/entrypoints.h | 9 ++++++++- src/settings/modules/GeneralSettings.cpp | 6 ++++-- src/settings/modules/GeneralSettings.h | 2 +- src/utility/storm.h | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index bbb9d6404..359d58d13 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -53,6 +53,11 @@ namespace storm { void verifySymbolicModelWithAbstractionRefinementEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Abstraction Refinement is not yet implemented."); } + + template + void verifySymbolicModelWithLearningEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { + STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Abstraction Refinement is not yet implemented."); + } template void verifySymbolicModelWithHybridEngine(std::shared_ptr> model, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { @@ -120,6 +125,8 @@ namespace storm { if (settings.getEngine() == storm::settings::modules::GeneralSettings::Engine::AbstractionRefinement) { verifySymbolicModelWithAbstractionRefinementEngine(program, formulas, onlyInitialStatesRelevant); + } else if (settings.getEngine() == storm::settings::modules::GeneralSettings::Engine::Learning) { + verifySymbolicModelWithLearningEngine(program, formulas, onlyInitialStatesRelevant); } else { storm::storage::ModelFormulasPair modelFormulasPair = buildSymbolicModel(program, formulas); STORM_LOG_THROW(modelFormulasPair.model != nullptr, storm::exceptions::InvalidStateException, @@ -136,7 +143,7 @@ namespace storm { if (modelFormulasPair.model->isSparseModel()) { if (storm::settings::generalSettings().isCounterexampleSet()) { // If we were requested to generate a counterexample, we now do so for each formula. - for (auto const &formula : modelFormulasPair.formulas) { + for (auto const& formula : modelFormulasPair.formulas) { generateCounterexample(program, modelFormulasPair.model->as>(), formula); } } else { diff --git a/src/settings/modules/GeneralSettings.cpp b/src/settings/modules/GeneralSettings.cpp index 95a48931b..122cb23ad 100644 --- a/src/settings/modules/GeneralSettings.cpp +++ b/src/settings/modules/GeneralSettings.cpp @@ -96,9 +96,9 @@ namespace storm { .addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "The file from which to read the choice labels.").addValidationFunctionString(storm::settings::ArgumentValidators::existingReadableFileValidator()).build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, dontFixDeadlockOptionName, false, "If the model contains deadlock states, they need to be fixed by setting this option.").setShortName(dontFixDeadlockOptionShortName).build()); - std::vector engines = {"sparse", "hybrid", "dd", "abs"}; + std::vector engines = {"sparse", "hybrid", "dd", "learn", "abs"}; this->addOption(storm::settings::OptionBuilder(moduleName, engineOptionName, false, "Sets which engine is used for model building and model checking.").setShortName(engineOptionShortName) - .addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the engine to use. Available are {sparse, hybrid, dd, ar}.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(engines)).setDefaultValueString("sparse").build()).build()); + .addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the engine to use. Available are {sparse, hybrid, dd, learn, abs}.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(engines)).setDefaultValueString("sparse").build()).build()); std::vector linearEquationSolver = {"gmm++", "native"}; this->addOption(storm::settings::OptionBuilder(moduleName, eqSolverOptionName, false, "Sets which solver is preferred for solving systems of linear equations.") @@ -346,6 +346,8 @@ namespace storm { engine = GeneralSettings::Engine::Hybrid; } else if (engineStr == "dd") { engine = GeneralSettings::Engine::Dd; + } else if (engineStr == "learn") { + engine = GeneralSettings::Engine::Learning; } else if (engineStr == "abs") { engine = GeneralSettings::Engine::AbstractionRefinement; } else { diff --git a/src/settings/modules/GeneralSettings.h b/src/settings/modules/GeneralSettings.h index 8144c0a1f..f2cbdca47 100644 --- a/src/settings/modules/GeneralSettings.h +++ b/src/settings/modules/GeneralSettings.h @@ -27,7 +27,7 @@ namespace storm { // An enumeration of all engines. enum class Engine { - Sparse, Hybrid, Dd, AbstractionRefinement + Sparse, Hybrid, Dd, Learning, AbstractionRefinement }; /*! diff --git a/src/utility/storm.h b/src/utility/storm.h index b01b03516..a7a717b8d 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -254,7 +254,7 @@ namespace storm { STORM_LOG_THROW(ddModel != nullptr, storm::exceptions::InvalidArgumentException, "Dd engine requires a dd input model"); return verifySymbolicModelWithDdEngine(ddModel, formula, onlyInitialStatesRelevant); } - case storm::settings::modules::GeneralSettings::Engine::AbstractionRefinement: { + default: { STORM_LOG_ASSERT(false, "This position should not be reached, as at this point no model has been built."); } } From 7dee6d3da281302a6309927a2fd79a23be3462af Mon Sep 17 00:00:00 2001 From: dehnert Date: Mon, 14 Mar 2016 16:04:07 +0100 Subject: [PATCH 02/31] started on learning-based MDP model checking Former-commit-id: 9a901e619b0f66553cc90abb41ef45bd0b5b8a7b --- SparseLearningModelChecker.cpp | 0 src/cli/entrypoints.h | 22 ++++++++++++++++++- .../reachability/SparseLearningModelChecker.h | 0 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 SparseLearningModelChecker.cpp create mode 100644 src/modelchecker/reachability/SparseLearningModelChecker.h diff --git a/SparseLearningModelChecker.cpp b/SparseLearningModelChecker.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index 359d58d13..a467825e9 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -56,7 +56,27 @@ namespace storm { template void verifySymbolicModelWithLearningEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { - STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Abstraction Refinement is not yet implemented."); + + for (auto const& formula : formulas) { + STORM_LOG_THROW(model->getType() == storm::models::ModelType::Dtmc || model->getType() == storm::models::ModelType::Mdp, storm::exceptions::InvalidSettingsException, "Currently learning-based verification is only available for DTMCs and MDPs."); + std::cout << std::endl << "Model checking property: " << *formula << " ..."; + storm::modelchecker::CheckTask task(*formula, onlyInitialStatesRelevant); + storm::modelchecker::SparseLearningModelChecker checker(program); + std::unique_ptr result; + if (checker.canHandle(formula)) { + std::unique_ptr result = checker.check(task); + } else { + std::cout << " skipped, because the formula cannot be handled by the selected engine/method." << std::endl; + } + if (result) { + std::cout << " done." << std::endl; + std::cout << "Result (initial states): "; + result->filter(storm::modelchecker::ExplicitQualitativeCheckResult(model->getInitialStates())); + std::cout << *result << std::endl; + } else { + std::cout << " skipped, because the modelling formalism is currently unsupported." << std::endl; + } + } } template diff --git a/src/modelchecker/reachability/SparseLearningModelChecker.h b/src/modelchecker/reachability/SparseLearningModelChecker.h new file mode 100644 index 000000000..e69de29bb From ca354cffe4e771da62777e6a102b3e45eeb20770 Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 17 Mar 2016 13:12:24 +0100 Subject: [PATCH 03/31] moved preprocessing of PRISM program to utility to make it accessible from learning-based model checker Former-commit-id: 704dde9ec5bfb6c7af4a368d39fcd475108528f7 --- src/builder/ExplicitPrismModelBuilder.cpp | 52 +---- src/cli/entrypoints.h | 16 +- .../reachability/SparseLearningModelChecker.h | 0 .../SparseMdpLearningModelChecker.cpp | 28 +++ .../SparseMdpLearningModelChecker.h | 29 +++ src/utility/prism.cpp | 66 +++++- src/utility/prism.h | 190 +----------------- src/utility/storm.h | 1 + 8 files changed, 141 insertions(+), 241 deletions(-) delete mode 100644 src/modelchecker/reachability/SparseLearningModelChecker.h create mode 100644 src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp create mode 100644 src/modelchecker/reachability/SparseMdpLearningModelChecker.h diff --git a/src/builder/ExplicitPrismModelBuilder.cpp b/src/builder/ExplicitPrismModelBuilder.cpp index ef53b9e43..94b1e0f46 100644 --- a/src/builder/ExplicitPrismModelBuilder.cpp +++ b/src/builder/ExplicitPrismModelBuilder.cpp @@ -182,57 +182,7 @@ namespace storm { } template - ExplicitPrismModelBuilder::ExplicitPrismModelBuilder(storm::prism::Program const& program, Options const& options) : program(program), options(options) { - // Start by defining the undefined constants in the model. - if (options.constantDefinitions) { - this->program = program.defineUndefinedConstants(options.constantDefinitions.get()); - } else { - this->program = program; - } - - // If the program still contains undefined constants and we are not in a parametric setting, assemble an appropriate error message. - if (!std::is_same::value && this->program.hasUndefinedConstants()) { - std::vector> undefinedConstants = this->program.getUndefinedConstants(); - std::stringstream stream; - bool printComma = false; - for (auto const& constant : undefinedConstants) { - if (printComma) { - stream << ", "; - } else { - printComma = true; - } - stream << constant.get().getName() << " (" << constant.get().getType() << ")"; - } - stream << "."; - STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Program still contains these undefined constants: " + stream.str()); - } else if (std::is_same::value && !this->program.hasUndefinedConstantsOnlyInUpdateProbabilitiesAndRewards()) { - STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The program contains undefined constants that appear in some places other than update probabilities and reward value expressions, which is not admitted."); - } - - // If the set of labels we are supposed to built is restricted, we need to remove the other labels from the program. - if (options.labelsToBuild) { - if (!options.buildAllLabels) { - this->program.filterLabels(options.labelsToBuild.get()); - } - } - - // If we need to build labels for expressions that may appear in some formula, we need to add appropriate - // labels to the program. - if (options.expressionLabels) { - std::map constantsSubstitution = this->program.getConstantsSubstitution(); - - for (auto const& expression : options.expressionLabels.get()) { - std::stringstream stream; - stream << expression.substitute(constantsSubstitution); - std::string name = stream.str(); - if (!this->program.hasLabel(name)) { - this->program.addLabel(name, expression); - } - } - } - - // Now that the program is fixed, we we need to substitute all constants with their concrete value. - this->program = this->program.substituteConstants(); + ExplicitPrismModelBuilder::ExplicitPrismModelBuilder(storm::prism::Program const& program, Options const& options) : program(storm::utility::prism::preprocessProgram(program, options.constantDefinitions, !options.buildAllLabels ? options.labelsToBuild : boost::none, options.expressionLabels)), options(options) { // Create the variable information for the transformed program. this->variableInformation = VariableInformation(this->program); diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index a467825e9..ba863af43 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -58,12 +58,12 @@ namespace storm { void verifySymbolicModelWithLearningEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { for (auto const& formula : formulas) { - STORM_LOG_THROW(model->getType() == storm::models::ModelType::Dtmc || model->getType() == storm::models::ModelType::Mdp, storm::exceptions::InvalidSettingsException, "Currently learning-based verification is only available for DTMCs and MDPs."); + STORM_LOG_THROW(program.getModelType() == storm::prism::Program::ModelType::DTMC || program.getModelType() == storm::prism::Program::ModelType::MDP, storm::exceptions::InvalidSettingsException, "Currently learning-based verification is only available for DTMCs and MDPs."); std::cout << std::endl << "Model checking property: " << *formula << " ..."; - storm::modelchecker::CheckTask task(*formula, onlyInitialStatesRelevant); - storm::modelchecker::SparseLearningModelChecker checker(program); + storm::modelchecker::CheckTask task(*formula, onlyInitialStatesRelevant); + storm::modelchecker::SparseMdpLearningModelChecker checker(program); std::unique_ptr result; - if (checker.canHandle(formula)) { + if (checker.canHandle(task)) { std::unique_ptr result = checker.check(task); } else { std::cout << " skipped, because the formula cannot be handled by the selected engine/method." << std::endl; @@ -71,13 +71,19 @@ namespace storm { if (result) { std::cout << " done." << std::endl; std::cout << "Result (initial states): "; - result->filter(storm::modelchecker::ExplicitQualitativeCheckResult(model->getInitialStates())); std::cout << *result << std::endl; } else { std::cout << " skipped, because the modelling formalism is currently unsupported." << std::endl; } } } + +#ifdef STORM_HAVE_CARL + template<> + void verifySymbolicModelWithLearningEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant) { + STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Learning-based verification does currently not support parametric models."); + } +#endif template void verifySymbolicModelWithHybridEngine(std::shared_ptr> model, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { diff --git a/src/modelchecker/reachability/SparseLearningModelChecker.h b/src/modelchecker/reachability/SparseLearningModelChecker.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp new file mode 100644 index 000000000..0316d6bc4 --- /dev/null +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -0,0 +1,28 @@ +#include "src/modelchecker/reachability/SparseMdpLearningModelChecker.h" + +#include "src/logic/FragmentSpecification.h" + +#include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" + +namespace storm { + namespace modelchecker { + template + SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(program) { + // Intentionally left empty. + } + + template + bool SparseMdpLearningModelChecker::canHandle(CheckTask const& checkTask) const { + storm::logic::Formula const& formula = checkTask.getFormula(); + storm::logic::FragmentSpecification fragment = storm::logic::propositional().setProbabilityOperatorsAllowed(true).setReachabilityProbabilityFormulasAllowed(true); + return formula.isInFragment(fragment); + } + + template + std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { + return nullptr; + } + + template class SparseMdpLearningModelChecker; + } +} \ No newline at end of file diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h new file mode 100644 index 000000000..d398c6be3 --- /dev/null +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -0,0 +1,29 @@ +#ifndef STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ +#define STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ + +#include "src/modelchecker/AbstractModelChecker.h" + +#include "src/storage/prism/Program.h" + +#include "src/utility/constants.h" + +namespace storm { + namespace modelchecker { + + template + class SparseMdpLearningModelChecker : public AbstractModelChecker { + public: + SparseMdpLearningModelChecker(storm::prism::Program const& program); + + virtual bool canHandle(CheckTask const& checkTask) const override; + + virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; + + private: + // The program that defines the model to check. + storm::prism::Program program; + }; + } +} + +#endif /* STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ */ \ No newline at end of file diff --git a/src/utility/prism.cpp b/src/utility/prism.cpp index 5901d3434..822e01fdc 100644 --- a/src/utility/prism.cpp +++ b/src/utility/prism.cpp @@ -1,13 +1,72 @@ #include "src/utility/prism.h" + +#include "src/adapters/CarlAdapter.h" + #include "src/storage/expressions/ExpressionManager.h" #include "src/storage/prism/Program.h" +#include "src/utility/macros.h" + #include "src/exceptions/InvalidArgumentException.h" -#include "macros.h" namespace storm { namespace utility { namespace prism { + + template + storm::prism::Program preprocessProgram(storm::prism::Program const& program, boost::optional> const& constantDefinitions, boost::optional> const& restrictedLabelSet, boost::optional> const& expressionLabels) { + storm::prism::Program result; + + // Start by defining the undefined constants in the model. + if (constantDefinitions) { + result = program.defineUndefinedConstants(constantDefinitions.get()); + } else { + result = program; + } + + // If the program still contains undefined constants and we are not in a parametric setting, assemble an appropriate error message. + if (!std::is_same::value && result.hasUndefinedConstants()) { + std::vector> undefinedConstants = result.getUndefinedConstants(); + std::stringstream stream; + bool printComma = false; + for (auto const& constant : undefinedConstants) { + if (printComma) { + stream << ", "; + } else { + printComma = true; + } + stream << constant.get().getName() << " (" << constant.get().getType() << ")"; + } + stream << "."; + STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Program still contains these undefined constants: " + stream.str()); + } else if (std::is_same::value && !result.hasUndefinedConstantsOnlyInUpdateProbabilitiesAndRewards()) { + STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The program contains undefined constants that appear in some places other than update probabilities and reward value expressions, which is not admitted."); + } + + // If the set of labels we are supposed to built is restricted, we need to remove the other labels from the program. + if (restrictedLabelSet) { + result.filterLabels(restrictedLabelSet.get()); + } + + // Build new labels. + if (expressionLabels) { + std::map constantsSubstitution = result.getConstantsSubstitution(); + + for (auto const& expression : expressionLabels.get()) { + std::stringstream stream; + stream << expression.substitute(constantsSubstitution); + std::string name = stream.str(); + if (!result.hasLabel(name)) { + result.addLabel(name, expression); + } + } + } + + // Now that the program is fixed, we we need to substitute all constants with their concrete value. + result = result.substituteConstants(); + return result; + } + std::map parseConstantDefinitionString(storm::prism::Program const& program, std::string const& constantDefinitionString) { std::map constantDefinitions; std::set definedConstants; @@ -64,6 +123,11 @@ namespace storm { return constantDefinitions; } + + template storm::prism::Program preprocessProgram(storm::prism::Program const& program, boost::optional> const& constantDefinitions, boost::optional> const& restrictedLabelSet, boost::optional> const& expressionLabels); + + template storm::prism::Program preprocessProgram(storm::prism::Program const& program, boost::optional> const& constantDefinitions, boost::optional> const& restrictedLabelSet, boost::optional> const& expressionLabels); + } } } \ No newline at end of file diff --git a/src/utility/prism.h b/src/utility/prism.h index f894b464f..c6a8918b9 100644 --- a/src/utility/prism.h +++ b/src/utility/prism.h @@ -1,13 +1,11 @@ #ifndef STORM_UTILITY_PRISM_H_ #define STORM_UTILITY_PRISM_H_ -#include #include -#include -#include - -#include "src/utility/OsDetection.h" +#include +#include +#include namespace storm { namespace expressions { @@ -21,185 +19,9 @@ namespace storm { namespace utility { namespace prism { - // A structure holding information about a particular choice. - template> - struct Choice { - public: - Choice(uint_fast64_t actionIndex = 0, bool createChoiceLabels = false) : distribution(), actionIndex(actionIndex), choiceLabels(nullptr) { - if (createChoiceLabels) { - choiceLabels = std::shared_ptr>(new boost::container::flat_set()); - } - } - - Choice(Choice const& other) = default; - Choice& operator=(Choice const& other) = default; -#ifndef WINDOWS - Choice(Choice&& other) = default; - Choice& operator=(Choice&& other) = default; -#endif - - /*! - * Returns an iterator to the first element of this choice. - * - * @return An iterator to the first element of this choice. - */ - typename std::map::iterator begin() { - return distribution.begin(); - } - - /*! - * Returns an iterator to the first element of this choice. - * - * @return An iterator to the first element of this choice. - */ - typename std::map::const_iterator begin() const { - return distribution.cbegin(); - } - - /*! - * Returns an iterator that points past the elements of this choice. - * - * @return An iterator that points past the elements of this choice. - */ - typename std::map::iterator end() { - return distribution.end(); - } - - /*! - * Returns an iterator that points past the elements of this choice. - * - * @return An iterator that points past the elements of this choice. - */ - typename std::map::const_iterator end() const { - return distribution.cend(); - } - - /*! - * Returns an iterator to the element with the given key, if there is one. Otherwise, the iterator points to - * distribution.end(). - * - * @param value The value to find. - * @return An iterator to the element with the given key, if there is one. - */ - typename std::map::iterator find(uint_fast64_t value) { - return distribution.find(value); - } - - /*! - * Inserts the contents of this object to the given output stream. - * - * @param out The stream in which to insert the contents. - */ - friend std::ostream& operator<<(std::ostream& out, Choice const& choice) { - out << "<"; - for (auto const& stateProbabilityPair : choice.distribution) { - out << stateProbabilityPair.first << " : " << stateProbabilityPair.second << ", "; - } - out << ">"; - return out; - } - - /*! - * Adds the given label to the labels associated with this choice. - * - * @param label The label to associate with this choice. - */ - void addChoiceLabel(uint_fast64_t label) { - choiceLabels->insert(label); - } - - /*! - * Adds the given label set to the labels associated with this choice. - * - * @param labelSet The label set to associate with this choice. - */ - void addChoiceLabels(boost::container::flat_set const& labelSet) { - for (uint_fast64_t label : labelSet) { - addChoiceLabel(label); - } - } - - /*! - * Retrieves the set of labels associated with this choice. - * - * @return The set of labels associated with this choice. - */ - boost::container::flat_set const& getChoiceLabels() const { - return *choiceLabels; - } - - /*! - * Retrieves the index of the action of this choice. - * - * @return The index of the action of this choice. - */ - uint_fast64_t getActionIndex() const { - return actionIndex; - } - - /*! - * Retrieves the total mass of this choice. - * - * @return The total mass. - */ - ValueType getTotalMass() const { - return totalMass; - } - - /*! - * Retrieves the entry in the choice that is associated with the given state and creates one if none exists, - * yet. - * - * @param state The state for which to add the entry. - * @return A reference to the entry that is associated with the given state. - */ - ValueType& getOrAddEntry(uint_fast64_t state) { - auto stateProbabilityPair = distribution.find(state); - - if (stateProbabilityPair == distribution.end()) { - distribution[state] = ValueType(); - } - return distribution.at(state); - } - - /*! - * Retrieves the entry in the choice that is associated with the given state and creates one if none exists, - * yet. - * - * @param state The state for which to add the entry. - * @return A reference to the entry that is associated with the given state. - */ - ValueType const& getOrAddEntry(uint_fast64_t state) const { - auto stateProbabilityPair = distribution.find(state); - - if (stateProbabilityPair == distribution.end()) { - distribution[state] = ValueType(); - } - return distribution.at(state); - } - - void addProbability(KeyType state, ValueType value) { - totalMass += value; - distribution[state] += value; - } - - std::size_t size() const { - return distribution.size(); - } - - private: - // The distribution that is associated with the choice. - std::map distribution; - - // The total probability mass (or rates) of this choice. - ValueType totalMass; - - // The index of the action name. - uint_fast64_t actionIndex; - - // The labels that are associated with this choice. - std::shared_ptr> choiceLabels; - }; + + template + storm::prism::Program preprocessProgram(storm::prism::Program const& program, boost::optional> const& constantDefinitions = boost::none, boost::optional> const& restrictedLabelSet = boost::none, boost::optional> const& expressionLabels = boost::none); std::map parseConstantDefinitionString(storm::prism::Program const& program, std::string const& constantDefinitionString); diff --git a/src/utility/storm.h b/src/utility/storm.h index c7afeb523..aca1a1cac 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -57,6 +57,7 @@ #include "src/modelchecker/prctl/SymbolicDtmcPrctlModelChecker.h" #include "src/modelchecker/prctl/SymbolicMdpPrctlModelChecker.h" #include "src/modelchecker/reachability/SparseDtmcEliminationModelChecker.h" +#include "src/modelchecker/reachability/SparseMdpLearningModelChecker.h" #include "src/modelchecker/csl/SparseCtmcCslModelChecker.h" #include "src/modelchecker/csl/HybridCtmcCslModelChecker.h" #include "src/modelchecker/csl/SparseMarkovAutomatonCslModelChecker.h" From 1fb943b65812ec17c1f2d54f751ddec0176f3d0f Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 17 Mar 2016 16:31:37 +0100 Subject: [PATCH 04/31] moved some internal structs from model builder to their own files to make them reusable Former-commit-id: a354059fe8b7ec7d00d51e3122ffedeeea946960 --- src/builder/ExplicitPrismModelBuilder.cpp | 66 +++++++------------ src/builder/ExplicitPrismModelBuilder.h | 51 ++------------ .../SparseMdpLearningModelChecker.cpp | 23 +++++-- .../SparseMdpLearningModelChecker.h | 13 ++++ src/models/sparse/StateAnnotation.h | 13 ++-- src/storage/sparse/StateStorage.cpp | 15 +++++ src/storage/sparse/StateStorage.h | 35 ++++++++++ src/storage/sparse/StateValuations.cpp | 17 +++++ src/storage/sparse/StateValuations.h | 33 ++++++++++ 9 files changed, 167 insertions(+), 99 deletions(-) create mode 100644 src/storage/sparse/StateStorage.cpp create mode 100644 src/storage/sparse/StateStorage.h create mode 100644 src/storage/sparse/StateValuations.cpp create mode 100644 src/storage/sparse/StateValuations.h diff --git a/src/builder/ExplicitPrismModelBuilder.cpp b/src/builder/ExplicitPrismModelBuilder.cpp index 94b1e0f46..e6da6ab7e 100644 --- a/src/builder/ExplicitPrismModelBuilder.cpp +++ b/src/builder/ExplicitPrismModelBuilder.cpp @@ -62,39 +62,24 @@ namespace storm { // The state-action reward vector. std::vector stateActionRewardVector; }; - - template - ExplicitPrismModelBuilder::StateInformation::StateInformation(uint_fast64_t numberOfStates) : valuations(numberOfStates) { - // Intentionally left empty. - } - - template - ExplicitPrismModelBuilder::InternalStateInformation::InternalStateInformation() : stateStorage(64, 10), initialStateIndices(), bitsPerState(64), numberOfStates() { - // Intentionally left empty. - } - - template - ExplicitPrismModelBuilder::InternalStateInformation::InternalStateInformation(uint64_t bitsPerState) : stateStorage(bitsPerState, 10000000), initialStateIndices(), bitsPerState(bitsPerState), numberOfStates() { - // Intentionally left empty. - } - + template ExplicitPrismModelBuilder::ModelComponents::ModelComponents() : transitionMatrix(), stateLabeling(), rewardModels(), choiceLabeling() { // Intentionally left empty. } template - ExplicitPrismModelBuilder::Options::Options() : explorationOrder(storm::settings::generalSettings().getExplorationOrder()), buildCommandLabels(false), buildAllRewardModels(true), buildStateInformation(false), rewardModelsToBuild(), constantDefinitions(), buildAllLabels(true), labelsToBuild(), expressionLabels(), terminalStates(), negatedTerminalStates() { + ExplicitPrismModelBuilder::Options::Options() : explorationOrder(storm::settings::generalSettings().getExplorationOrder()), buildCommandLabels(false), buildAllRewardModels(true), buildStateValuations(false), rewardModelsToBuild(), constantDefinitions(), buildAllLabels(true), labelsToBuild(), expressionLabels(), terminalStates(), negatedTerminalStates() { // Intentionally left empty. } template - ExplicitPrismModelBuilder::Options::Options(storm::logic::Formula const& formula) : explorationOrder(storm::settings::generalSettings().getExplorationOrder()), buildCommandLabels(false), buildAllRewardModels(false), buildStateInformation(false), rewardModelsToBuild(), constantDefinitions(), buildAllLabels(false), labelsToBuild(std::set()), expressionLabels(std::vector()), terminalStates(), negatedTerminalStates() { + ExplicitPrismModelBuilder::Options::Options(storm::logic::Formula const& formula) : explorationOrder(storm::settings::generalSettings().getExplorationOrder()), buildCommandLabels(false), buildAllRewardModels(false), buildStateValuations(false), rewardModelsToBuild(), constantDefinitions(), buildAllLabels(false), labelsToBuild(std::set()), expressionLabels(std::vector()), terminalStates(), negatedTerminalStates() { this->preserveFormula(formula); } template - ExplicitPrismModelBuilder::Options::Options(std::vector> const& formulas) : explorationOrder(storm::settings::generalSettings().getExplorationOrder()), buildCommandLabels(false), buildAllRewardModels(false), buildStateInformation(false), rewardModelsToBuild(), constantDefinitions(), buildAllLabels(false), labelsToBuild(), expressionLabels(), terminalStates(), negatedTerminalStates() { + ExplicitPrismModelBuilder::Options::Options(std::vector> const& formulas) : explorationOrder(storm::settings::generalSettings().getExplorationOrder()), buildCommandLabels(false), buildAllRewardModels(false), buildStateValuations(false), rewardModelsToBuild(), constantDefinitions(), buildAllLabels(false), labelsToBuild(), expressionLabels(), terminalStates(), negatedTerminalStates() { if (formulas.empty()) { this->buildAllRewardModels = true; this->buildAllLabels = true; @@ -182,19 +167,14 @@ namespace storm { } template - ExplicitPrismModelBuilder::ExplicitPrismModelBuilder(storm::prism::Program const& program, Options const& options) : program(storm::utility::prism::preprocessProgram(program, options.constantDefinitions, !options.buildAllLabels ? options.labelsToBuild : boost::none, options.expressionLabels)), options(options) { - - // Create the variable information for the transformed program. - this->variableInformation = VariableInformation(this->program); - - // Create the internal state storage. - this->internalStateInformation = InternalStateInformation(variableInformation.getTotalBitOffset(true)); + ExplicitPrismModelBuilder::ExplicitPrismModelBuilder(storm::prism::Program const& program, Options const& options) : program(storm::utility::prism::preprocessProgram(program, options.constantDefinitions, !options.buildAllLabels ? options.labelsToBuild : boost::none, options.expressionLabels)), options(options), variableInformation(this->program), stateStorage(variableInformation.getTotalBitOffset(true)) { + // Intentionally left empty. } template - typename ExplicitPrismModelBuilder::StateInformation const& ExplicitPrismModelBuilder::getStateInformation() const { - STORM_LOG_THROW(static_cast(stateInformation), storm::exceptions::InvalidOperationException, "The state information was not properly build."); - return stateInformation.get(); + storm::storage::sparse::StateValuations const& ExplicitPrismModelBuilder::getStateValuations() const { + STORM_LOG_THROW(static_cast(stateValuations), storm::exceptions::InvalidOperationException, "The state information was not properly build."); + return stateValuations.get(); } template @@ -261,10 +241,10 @@ namespace storm { template StateType ExplicitPrismModelBuilder::getOrAddStateIndex(CompressedState const& state) { - uint32_t newIndex = internalStateInformation.numberOfStates; + uint32_t newIndex = stateStorage.numberOfStates; // Check, if the state was already registered. - std::pair actualIndexBucketPair = internalStateInformation.stateStorage.findOrAddAndGetBucket(state, newIndex); + std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); if (actualIndexBucketPair.first == newIndex) { if (options.explorationOrder == ExplorationOrder::Dfs) { @@ -277,7 +257,7 @@ namespace storm { } else { STORM_LOG_ASSERT(false, "Invalid exploration order."); } - ++internalStateInformation.numberOfStates; + ++stateStorage.numberOfStates; } return actualIndexBucketPair.first; @@ -311,7 +291,7 @@ namespace storm { } // Let the generator create all initial states. - this->internalStateInformation.initialStateIndices = generator.getInitialStates(stateToIdCallback); + this->stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback); // Now explore the current state until there is no more reachable state. uint_fast64_t currentRowGroup = 0; @@ -321,7 +301,7 @@ namespace storm { while (!statesToExplore.empty()) { // Get the first state in the queue. CompressedState currentState = statesToExplore.front(); - StateType currentIndex = internalStateInformation.stateStorage.getValue(currentState); + StateType currentIndex = stateStorage.stateToId.getValue(currentState); statesToExplore.pop_front(); // If the exploration order differs from breadth-first, we remember that this row group was actually @@ -426,13 +406,13 @@ namespace storm { transitionMatrixBuilder.replaceColumns(remapping, 0); // Fix (b). - std::vector newInitialStateIndices(this->internalStateInformation.initialStateIndices.size()); - std::transform(this->internalStateInformation.initialStateIndices.begin(), this->internalStateInformation.initialStateIndices.end(), newInitialStateIndices.begin(), [&remapping] (StateType const& state) { return remapping[state]; } ); + std::vector newInitialStateIndices(this->stateStorage.initialStateIndices.size()); + std::transform(this->stateStorage.initialStateIndices.begin(), this->stateStorage.initialStateIndices.end(), newInitialStateIndices.begin(), [&remapping] (StateType const& state) { return remapping[state]; } ); std::sort(newInitialStateIndices.begin(), newInitialStateIndices.end()); - this->internalStateInformation.initialStateIndices = std::move(newInitialStateIndices); + this->stateStorage.initialStateIndices = std::move(newInitialStateIndices); // Fix (c). - this->internalStateInformation.stateStorage.remap([&remapping] (StateType const& state) { return remapping[state]; } ); + this->stateStorage.stateToId.remap([&remapping] (StateType const& state) { return remapping[state]; } ); } return choiceLabels; @@ -497,10 +477,10 @@ namespace storm { modelComponents.stateLabeling = buildStateLabeling(); // Finally -- if requested -- build the state information that can be retrieved from the outside. - if (options.buildStateInformation) { - stateInformation = StateInformation(internalStateInformation.numberOfStates); - for (auto const& bitVectorIndexPair : internalStateInformation.stateStorage) { - stateInformation.get().valuations[bitVectorIndexPair.second] = unpackStateIntoValuation(bitVectorIndexPair.first); + if (options.buildStateValuations) { + stateValuations = storm::storage::sparse::StateValuations(stateStorage.numberOfStates); + for (auto const& bitVectorIndexPair : stateStorage.stateToId) { + stateValuations.get().valuations[bitVectorIndexPair.second] = unpackStateIntoValuation(bitVectorIndexPair.first); } } @@ -510,7 +490,7 @@ namespace storm { template storm::models::sparse::StateLabeling ExplicitPrismModelBuilder::buildStateLabeling() { storm::generator::PrismStateLabelingGenerator generator(program, variableInformation); - return generator.generate(internalStateInformation.stateStorage, internalStateInformation.initialStateIndices); + return generator.generate(stateStorage.stateToId, stateStorage.initialStateIndices); } // Explicitly instantiate the class. diff --git a/src/builder/ExplicitPrismModelBuilder.h b/src/builder/ExplicitPrismModelBuilder.h index ab2eb9815..8cc724de2 100644 --- a/src/builder/ExplicitPrismModelBuilder.h +++ b/src/builder/ExplicitPrismModelBuilder.h @@ -20,6 +20,8 @@ #include "src/models/sparse/Model.h" #include "src/models/sparse/StateLabeling.h" #include "src/storage/SparseMatrix.h" +#include "src/storage/sparse/StateValuations.h" +#include "src/storage/sparse/StateStorage.h" #include "src/settings/SettingsManager.h" #include "src/utility/prism.h" @@ -45,42 +47,6 @@ namespace storm { template, typename StateType = uint32_t> class ExplicitPrismModelBuilder { public: - // A structure holding information about the reachable state space while building it. - struct InternalStateInformation { - // Builds an empty state information. - InternalStateInformation(); - - // Creates a state information structure for storing states of the given bit width. - InternalStateInformation(uint64_t bitsPerState); - - // This member stores all the states and maps them to their unique indices. - storm::storage::BitVectorHashMap stateStorage; - - // A list of initial states in terms of their global indices. - std::vector initialStateIndices; - - // The number of bits of each state. - uint64_t bitsPerState; - - // The number of states that were found in the exploration so far. - uint_fast64_t numberOfStates; - }; - - // A structure holding information about the reachable state space that can be retrieved from the outside. - struct StateInformation : public storm::models::sparse::StateAnnotation { - /*! - * Constructs a state information object for the given number of states. - */ - StateInformation(uint_fast64_t numberOfStates); - - // A mapping from state indices to their variable valuations. - std::vector valuations; - - std::string stateInfo(uint_fast64_t state) const override { - return valuations[state].toString(); - } - }; - // A structure holding the individual components of a model. struct ModelComponents { ModelComponents(); @@ -163,7 +129,7 @@ namespace storm { // A flag that indicates whether or not to store the state information after successfully building the // model. If it is to be preserved, it can be retrieved via the appropriate methods after a successful // call to translateProgram. - bool buildStateInformation; + bool buildStateValuations; // A list of reward models to be build in case not all reward models are to be build. std::set rewardModelsToBuild; @@ -214,7 +180,7 @@ namespace storm { * * @return A structure that stores information about all reachable states. */ - StateInformation const& getStateInformation() const; + storm::storage::sparse::StateValuations const& getStateValuations() const; /*! * Retrieves the program that was actually translated (i.e. including constant substitutions etc.). @@ -233,7 +199,6 @@ namespace storm { * used after invoking this method. * * @param state A pointer to a state for which to retrieve the index. This must not be used after the call. - * @param internalStateInformation The information about the already explored part of the reachable state space. * @return A pair indicating whether the state was already discovered before and the state id of the state. */ StateType getOrAddStateIndex(CompressedState const& state); @@ -245,7 +210,6 @@ namespace storm { * @param variableInformation A structure containing information about the variables in the program. * @param transitionRewards A list of transition rewards that are to be considered in the transition reward * matrix. - * @param internalStateInformation A structure containing information about the states of the program. * @param deterministicModel A flag indicating whether the model is supposed to be deterministic or not. * @param transitionMatrix A reference to an initialized matrix which is filled with all transitions by this * function. @@ -270,9 +234,6 @@ namespace storm { /*! * Builds the state labeling for the given program. * - * @param program The program for which to build the state labeling. - * @param variableInformation Information about the variables in the program. - * @param internalStateInformation Information about the state space of the program. * @return The state labeling of the given program. */ storm::models::sparse::StateLabeling buildStateLabeling(); @@ -287,11 +248,11 @@ namespace storm { VariableInformation variableInformation; // Internal information about the states that were explored. - InternalStateInformation internalStateInformation; + storm::storage::sparse::StateStorage stateStorage; // This member holds information about reachable states that can be retrieved from the outside after a // successful build. - boost::optional stateInformation; + boost::optional stateValuations; // A set of states that still need to be explored. std::deque statesToExplore; diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 0316d6bc4..bdda7f3ed 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -2,27 +2,38 @@ #include "src/logic/FragmentSpecification.h" +#include "src/utility/prism.h" + #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" namespace storm { namespace modelchecker { - template - SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(program) { + template + SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram(program)), variableInformation(this->program), generator(program, variableInformation, false) { // Intentionally left empty. } - template - bool SparseMdpLearningModelChecker::canHandle(CheckTask const& checkTask) const { + template + bool SparseMdpLearningModelChecker::canHandle(CheckTask const& checkTask) const { storm::logic::Formula const& formula = checkTask.getFormula(); storm::logic::FragmentSpecification fragment = storm::logic::propositional().setProbabilityOperatorsAllowed(true).setReachabilityProbabilityFormulasAllowed(true); return formula.isInFragment(fragment); } - template - std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { + template + std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { + + // Create a callback for the next-state generator to enable it to request the index of states. + std::function stateToIdCallback = std::bind(&SparseMdpLearningModelChecker::getOrAddStateIndex, this, std::placeholders::_1); + return nullptr; } + template + typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::getOrAddStateIndex(storm::generator::CompressedState const& state) { + + } + template class SparseMdpLearningModelChecker; } } \ No newline at end of file diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index d398c6be3..02e53403b 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -5,6 +5,9 @@ #include "src/storage/prism/Program.h" +#include "src/generator/PrismNextStateGenerator.h" +#include "src/generator/VariableInformation.h" + #include "src/utility/constants.h" namespace storm { @@ -13,6 +16,8 @@ namespace storm { template class SparseMdpLearningModelChecker : public AbstractModelChecker { public: + typedef uint32_t StateType; + SparseMdpLearningModelChecker(storm::prism::Program const& program); virtual bool canHandle(CheckTask const& checkTask) const override; @@ -20,8 +25,16 @@ namespace storm { virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; private: + StateType getOrAddStateIndex(storm::generator::CompressedState const& state); + // The program that defines the model to check. storm::prism::Program program; + + // The variable information. + storm::generator::VariableInformation variableInformation; + + // A generator used to explore the model. + storm::generator::PrismNextStateGenerator generator; }; } } diff --git a/src/models/sparse/StateAnnotation.h b/src/models/sparse/StateAnnotation.h index 42067bf76..f608a5186 100644 --- a/src/models/sparse/StateAnnotation.h +++ b/src/models/sparse/StateAnnotation.h @@ -1,16 +1,19 @@ -#ifndef STORM_STATEANNOTATION_H -#define STORM_STATEANNOTATION_H +#ifndef STORM_MODELS_SPARSE_STATEANNOTATION_H_ +#define STORM_MODELS_SPARSE_STATEANNOTATION_H_ + +#include "src/storage/sparse/StateType.h" namespace storm { namespace models { namespace sparse { + class StateAnnotation { public: - virtual std::string stateInfo(uint_fast64_t s) const = 0; + virtual std::string stateInfo(storm::storage::sparse::state_type const& state) const = 0; }; + } } - } -#endif //STORM_STATEANNOTATION_H \ No newline at end of file +#endif /* STORM_MODELS_SPARSE_STATEANNOTATION_H_ */ \ No newline at end of file diff --git a/src/storage/sparse/StateStorage.cpp b/src/storage/sparse/StateStorage.cpp new file mode 100644 index 000000000..30c0eaace --- /dev/null +++ b/src/storage/sparse/StateStorage.cpp @@ -0,0 +1,15 @@ +#include "src/storage/sparse/StateStorage.h" + +namespace storm { + namespace storage { + namespace sparse { + + template + StateStorage::StateStorage(uint64_t bitsPerState) : stateToId(bitsPerState, 10000000), initialStateIndices(), bitsPerState(bitsPerState), numberOfStates() { + // Intentionally left empty. + } + + template class StateStorage; + } + } +} \ No newline at end of file diff --git a/src/storage/sparse/StateStorage.h b/src/storage/sparse/StateStorage.h new file mode 100644 index 000000000..79c8483ab --- /dev/null +++ b/src/storage/sparse/StateStorage.h @@ -0,0 +1,35 @@ +#ifndef STORM_STORAGE_SPARSE_STATESTORAGE_H_ +#define STORM_STORAGE_SPARSE_STATESTORAGE_H_ + +#include + +#include "src/storage/BitVectorHashMap.h" + +namespace storm { + namespace storage { + namespace sparse { + + // A structure holding information about the reachable state space while building it. + template + struct StateStorage { + // Creates an empty state storage structure for storing states of the given bit width. + StateStorage(uint64_t bitsPerState); + + // This member stores all the states and maps them to their unique indices. + storm::storage::BitVectorHashMap stateToId; + + // A list of initial states in terms of their global indices. + std::vector initialStateIndices; + + // The number of bits of each state. + uint64_t bitsPerState; + + // The number of states that were found in the exploration so far. + uint_fast64_t numberOfStates; + }; + + } + } +} + +#endif /* STORM_STORAGE_SPARSE_STATESTORAGE_H_ */ \ No newline at end of file diff --git a/src/storage/sparse/StateValuations.cpp b/src/storage/sparse/StateValuations.cpp new file mode 100644 index 000000000..f3f98818f --- /dev/null +++ b/src/storage/sparse/StateValuations.cpp @@ -0,0 +1,17 @@ +#include "src/storage/sparse/StateValuations.h" + +namespace storm { + namespace storage { + namespace sparse { + + StateValuations::StateValuations(state_type const& numberOfStates) : valuations(numberOfStates) { + // Intentionally left empty. + } + + std::string StateValuations::stateInfo(state_type const& state) const { + return valuations[state].toString(); + } + + } + } +} \ No newline at end of file diff --git a/src/storage/sparse/StateValuations.h b/src/storage/sparse/StateValuations.h new file mode 100644 index 000000000..b64620efd --- /dev/null +++ b/src/storage/sparse/StateValuations.h @@ -0,0 +1,33 @@ +#ifndef STORM_STORAGE_SPARSE_STATEVALUATIONS_H_ +#define STORM_STORAGE_SPARSE_STATEVALUATIONS_H_ + +#include +#include + +#include "src/storage/sparse/StateType.h" +#include "src/storage/expressions/SimpleValuation.h" + +#include "src/models/sparse/StateAnnotation.h" + +namespace storm { + namespace storage { + namespace sparse { + + // A structure holding information about the reachable state space that can be retrieved from the outside. + struct StateValuations : public storm::models::sparse::StateAnnotation { + /*! + * Constructs a state information object for the given number of states. + */ + StateValuations(state_type const& numberOfStates); + + // A mapping from state indices to their variable valuations. + std::vector valuations; + + virtual std::string stateInfo(state_type const& state) const override; + }; + + } + } +} + +#endif /* STORM_STORAGE_SPARSE_STATEVALUATIONS_H_ */ \ No newline at end of file From 8ed46ce1b8e023b4a39e320025beb66d2c4188e1 Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 17 Mar 2016 16:57:48 +0100 Subject: [PATCH 05/31] started on learning-based verification Former-commit-id: 24e9d81b1572c6ede5600be5bcd975240b84ced6 --- .../SparseMdpLearningModelChecker.cpp | 31 +++++++++++++++++-- .../SparseMdpLearningModelChecker.h | 5 +-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index bdda7f3ed..59ef3af0f 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -1,5 +1,10 @@ #include "src/modelchecker/reachability/SparseMdpLearningModelChecker.h" +#include "src/storage/SparseMatrix.h" +#include "src/storage/sparse/StateStorage.h" + +#include "src/generator/PrismNextStateGenerator.h" + #include "src/logic/FragmentSpecification.h" #include "src/utility/prism.h" @@ -9,7 +14,7 @@ namespace storm { namespace modelchecker { template - SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram(program)), variableInformation(this->program), generator(program, variableInformation, false) { + SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram(program)), variableInformation(this->program) { // Intentionally left empty. } @@ -17,14 +22,34 @@ namespace storm { bool SparseMdpLearningModelChecker::canHandle(CheckTask const& checkTask) const { storm::logic::Formula const& formula = checkTask.getFormula(); storm::logic::FragmentSpecification fragment = storm::logic::propositional().setProbabilityOperatorsAllowed(true).setReachabilityProbabilityFormulasAllowed(true); - return formula.isInFragment(fragment); + return formula.isInFragment(fragment) && checkTask.isOnlyInitialStatesRelevantSet(); } template std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { - // Create a callback for the next-state generator to enable it to request the index of states. std::function stateToIdCallback = std::bind(&SparseMdpLearningModelChecker::getOrAddStateIndex, this, std::placeholders::_1); + + // A container for the encountered states. + storm::storage::sparse::StateStorage stateStorage(variableInformation.getTotalBitOffset(true)); + + // A generator used to explore the model. + storm::generator::PrismNextStateGenerator generator(program, variableInformation, false); + + // A container that stores the transitions found so far. + std::vector>> matrix; + + // A vector storing where the row group of each state starts. + std::vector rowGroupIndices; + + // A vector storing the mapping from state ids to row groups. + std::vector stateToRowGroupMapping; + + // Vectors to store the lower/upper bounds for each action (in each state). + std::vector lowerBounds; + std::vector upperBounds; + + // Now perform the actual exploration loop. return nullptr; } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 02e53403b..bc9ed4241 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -5,7 +5,7 @@ #include "src/storage/prism/Program.h" -#include "src/generator/PrismNextStateGenerator.h" +#include "src/generator/CompressedState.h" #include "src/generator/VariableInformation.h" #include "src/utility/constants.h" @@ -32,9 +32,6 @@ namespace storm { // The variable information. storm::generator::VariableInformation variableInformation; - - // A generator used to explore the model. - storm::generator::PrismNextStateGenerator generator; }; } } From fd615289e098cb02e77fb96e833ad8546379d5fa Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 18 Mar 2016 17:28:11 +0100 Subject: [PATCH 06/31] outline of learning algorithm Former-commit-id: d770d1b7dcf2fb58d82ef32be56dd7ca02bc195e --- src/builder/ExplicitPrismModelBuilder.cpp | 3 +- src/generator/NextStateGenerator.h | 7 +- src/generator/PrismNextStateGenerator.cpp | 21 +++- src/generator/PrismNextStateGenerator.h | 7 +- .../SparseMdpLearningModelChecker.cpp | 112 ++++++++++++++++-- .../SparseMdpLearningModelChecker.h | 2 +- src/storage/SparseMatrix.cpp | 3 + 7 files changed, 137 insertions(+), 18 deletions(-) diff --git a/src/builder/ExplicitPrismModelBuilder.cpp b/src/builder/ExplicitPrismModelBuilder.cpp index e6da6ab7e..4820e1652 100644 --- a/src/builder/ExplicitPrismModelBuilder.cpp +++ b/src/builder/ExplicitPrismModelBuilder.cpp @@ -312,7 +312,8 @@ namespace storm { STORM_LOG_TRACE("Exploring state with id " << currentIndex << "."); - storm::generator::StateBehavior behavior = generator.expand(currentState, stateToIdCallback); + generator.load(currentState); + storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); // If there is no behavior, we might have to introduce a self-loop. if (behavior.empty()) { diff --git a/src/generator/NextStateGenerator.h b/src/generator/NextStateGenerator.h index 779a49ef3..de80bc83d 100644 --- a/src/generator/NextStateGenerator.h +++ b/src/generator/NextStateGenerator.h @@ -4,6 +4,8 @@ #include #include +#include "src/storage/expressions/Expression.h" + #include "src/generator/CompressedState.h" #include "src/generator/StateBehavior.h" @@ -16,7 +18,10 @@ namespace storm { virtual bool isDeterministicModel() const = 0; virtual std::vector getInitialStates(StateToIdCallback const& stateToIdCallback) = 0; - virtual StateBehavior expand(CompressedState const& state, StateToIdCallback const& stateToIdCallback) = 0; + + virtual void load(CompressedState const& state) = 0; + virtual StateBehavior expand(StateToIdCallback const& stateToIdCallback) = 0; + virtual bool satisfies(storm::expressions::Expression const& expression) = 0; }; } } diff --git a/src/generator/PrismNextStateGenerator.cpp b/src/generator/PrismNextStateGenerator.cpp index fac3be0f5..83fd4b6c8 100644 --- a/src/generator/PrismNextStateGenerator.cpp +++ b/src/generator/PrismNextStateGenerator.cpp @@ -10,7 +10,7 @@ namespace storm { namespace generator { template - PrismNextStateGenerator::PrismNextStateGenerator(storm::prism::Program const& program, VariableInformation const& variableInformation, bool buildChoiceLabeling) : program(program), selectedRewardModels(), buildChoiceLabeling(buildChoiceLabeling), variableInformation(variableInformation), evaluator(program.getManager()), comparator() { + PrismNextStateGenerator::PrismNextStateGenerator(storm::prism::Program const& program, VariableInformation const& variableInformation, bool buildChoiceLabeling) : program(program), selectedRewardModels(), buildChoiceLabeling(buildChoiceLabeling), variableInformation(variableInformation), evaluator(program.getManager()), state(nullptr), comparator() { // Intentionally left empty. } @@ -49,10 +49,21 @@ namespace storm { } template - StateBehavior PrismNextStateGenerator::expand(CompressedState const& state, StateToIdCallback const& stateToIdCallback) { + void PrismNextStateGenerator::load(CompressedState const& state) { // Since almost all subsequent operations are based on the evaluator, we load the state into it now. unpackStateIntoEvaluator(state, variableInformation, evaluator); - + + // Also, we need to store a pointer to the state itself, because we need to be able to access it when expanding it. + this->state = &state; + } + + template + bool PrismNextStateGenerator::satisfies(storm::expressions::Expression const& expression) { + return evaluator.asBool(expression); + } + + template + StateBehavior PrismNextStateGenerator::expand(StateToIdCallback const& stateToIdCallback) { // Prepare the result, in case we return early. StateBehavior result; @@ -76,8 +87,8 @@ namespace storm { } // Get all choices for the state. - std::vector> allChoices = getUnlabeledChoices(state, stateToIdCallback); - std::vector> allLabeledChoices = getLabeledChoices(state, stateToIdCallback); + std::vector> allChoices = getUnlabeledChoices(*this->state, stateToIdCallback); + std::vector> allLabeledChoices = getLabeledChoices(*this->state, stateToIdCallback); for (auto& choice : allLabeledChoices) { allChoices.push_back(std::move(choice)); } diff --git a/src/generator/PrismNextStateGenerator.h b/src/generator/PrismNextStateGenerator.h index d8eddc0ac..4eebf0dd3 100644 --- a/src/generator/PrismNextStateGenerator.h +++ b/src/generator/PrismNextStateGenerator.h @@ -31,7 +31,10 @@ namespace storm { virtual bool isDeterministicModel() const override; virtual std::vector getInitialStates(StateToIdCallback const& stateToIdCallback) override; - virtual StateBehavior expand(CompressedState const& state, StateToIdCallback const& stateToIdCallback) override; + + virtual void load(CompressedState const& state) override; + virtual StateBehavior expand(StateToIdCallback const& stateToIdCallback) override; + virtual bool satisfies(storm::expressions::Expression const& expression) override; private: /*! @@ -98,6 +101,8 @@ namespace storm { // An evaluator used to evaluate expressions. storm::expressions::ExpressionEvaluator evaluator; + CompressedState const* state; + // A comparator used to compare constants. storm::utility::ConstantsComparator comparator; }; diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 59ef3af0f..454c29ac1 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -11,6 +11,9 @@ #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "src/utility/macros.h" +#include "src/exceptions/NotSupportedException.h" + namespace storm { namespace modelchecker { template @@ -21,18 +24,47 @@ namespace storm { template bool SparseMdpLearningModelChecker::canHandle(CheckTask const& checkTask) const { storm::logic::Formula const& formula = checkTask.getFormula(); - storm::logic::FragmentSpecification fragment = storm::logic::propositional().setProbabilityOperatorsAllowed(true).setReachabilityProbabilityFormulasAllowed(true); + storm::logic::FragmentSpecification fragment = storm::logic::propositional().setProbabilityOperatorsAllowed(true).setReachabilityProbabilityFormulasAllowed(true).setNestedOperatorsAllowed(false); return formula.isInFragment(fragment) && checkTask.isOnlyInitialStatesRelevantSet(); } + template + void SparseMdpLearningModelChecker::updateProbabilities(StateType const& sourceStateId, uint32_t action, StateType const& targetStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const { + // Find out which row of the matrix we have to consider for the given action. + StateType sourceRowGroup = stateToRowGroupMapping[sourceStateId]; + StateType sourceRow = sourceRowGroup + action; + + // Compute the new lower/upper values of the action. + ValueType newLowerValue = storm::utility::zero(); + ValueType newUpperValue = storm::utility::zero(); + for (auto const& element : transitionMatrix[sourceRow]) { + newLowerValue += element.getValue() * upperBounds[stateToRowGroupMapping[element.getColumn()]]; + newUpperValue += element.getValue() * lowerBounds[stateToRowGroupMapping[element.getColumn()]]; + } + + // And set them as the current value. + lowerBounds[stateToRowGroupMapping[sourceStateId]] = newLowerValue; + upperBounds[stateToRowGroupMapping[sourceStateId]] = newUpperValue; + } + template std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { - // Create a callback for the next-state generator to enable it to request the index of states. - std::function stateToIdCallback = std::bind(&SparseMdpLearningModelChecker::getOrAddStateIndex, this, std::placeholders::_1); + storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); + storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); + STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); + storm::expressions::Expression targetStateExpression; + if (subformula.isAtomicExpressionFormula()) { + targetStateExpression = subformula.asAtomicExpressionFormula().getExpression(); + } else { + targetStateExpression = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); + } // A container for the encountered states. storm::storage::sparse::StateStorage stateStorage(variableInformation.getTotalBitOffset(true)); + // A container that stores the states that were already expanded. + storm::storage::BitVector expandedStates; + // A generator used to explore the model. storm::generator::PrismNextStateGenerator generator(program, variableInformation, false); @@ -48,17 +80,79 @@ namespace storm { // Vectors to store the lower/upper bounds for each action (in each state). std::vector lowerBounds; std::vector upperBounds; + + // Create a callback for the next-state generator to enable it to request the index of states. + std::function stateToIdCallback = [&stateStorage] (storm::generator::CompressedState const& state) -> StateType { + StateType newIndex = stateStorage.numberOfStates; + + // Check, if the state was already registered. + std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); + + if (actualIndexBucketPair.first == newIndex) { + ++stateStorage.numberOfStates; + } + + return actualIndexBucketPair.first; + }; + + stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback); + STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); + + // Now perform the actual sampling. + std::unordered_map unexploredStates; + std::vector> stateActionStack; + stateActionStack.push_back(std::make_pair(stateStorage.initialStateIndices.front(), 0)); + bool foundTargetState = false; - // Now perform the actual exploration loop. + while (!foundTargetState) { + StateType const& currentStateId = stateActionStack.back().first; + + // If the state is not yet expanded, we need to retrieve its behaviors. + if (!expandedStates.get(currentStateId)) { + // First, we need to get the compressed state back from the id. + auto it = unexploredStates.find(currentStateId); + STORM_LOG_ASSERT(it != unexploredStates.end(), "Unable to find unexplored state."); + storm::storage::BitVector currentState = it->second; + + // Before generating the behavior of the state, we need to determine whether it's a target state that + // does not need to be expanded. + generator.load(currentState); + if (generator.satisfies(targetStateExpression)) { + // If it's in fact a goal state, we need to go backwards in the stack and update the probabilities. + foundTargetState = true; + stateActionStack.pop_back(); + + while (!stateActionStack.empty()) { + updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBounds, upperBounds); + } + break; + } else { + // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. + storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); + + stateToRowGroupMapping.push_back(rowGroupIndices.size()); + rowGroupIndices.push_back(matrix.size()); + + // Next, we insert the behavior into our matrix structure. + for (auto const& choice : behavior) { + matrix.resize(matrix.size() + 1); + for (auto const& entry : choice) { + matrix.back().push_back(storm::storage::MatrixEntry(entry.first, entry.second)); + } + } + + // Now that we have explored the state, we can dispose of it. + unexploredStates.erase(it); + } + } + + // At this point, we can be sure that the state was expanded and that we can sample according to the probabilities. + // TODO: set action of topmost stack element + } return nullptr; } - template - typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::getOrAddStateIndex(storm::generator::CompressedState const& state) { - - } - template class SparseMdpLearningModelChecker; } } \ No newline at end of file diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index bc9ed4241..b181a44f6 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -25,7 +25,7 @@ namespace storm { virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; private: - StateType getOrAddStateIndex(storm::generator::CompressedState const& state); + void updateProbabilities(StateType const& sourceStateId, uint32_t action, StateType const& targetStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const; // The program that defines the model to check. storm::prism::Program program; diff --git a/src/storage/SparseMatrix.cpp b/src/storage/SparseMatrix.cpp index 9795dbe2d..da441214e 100644 --- a/src/storage/SparseMatrix.cpp +++ b/src/storage/SparseMatrix.cpp @@ -1319,6 +1319,9 @@ namespace storm { template std::ostream& operator<<(std::ostream& out, SparseMatrix const& matrix); template std::vector SparseMatrix::getPointwiseProductRowSumVector(storm::storage::SparseMatrix const& otherMatrix) const; template bool SparseMatrix::isSubmatrixOf(SparseMatrix const& matrix) const; + + template class MatrixEntry; + template std::ostream& operator<<(std::ostream& out, MatrixEntry const& entry); // float template class MatrixEntry::index_type, float>; From e6ec8d5b609a5f1fdfbadbcaa1d08161b5d9d75d Mon Sep 17 00:00:00 2001 From: dehnert Date: Mon, 21 Mar 2016 17:03:55 +0100 Subject: [PATCH 07/31] fixed formula building in some performance tests Former-commit-id: 1f6c5f67db38cc29090f562f2c3f0941c07551f5 --- .../SparseMdpLearningModelChecker.cpp | 1 + .../GmmxxDtmcPrctModelCheckerTest.cpp | 38 +++++++++--------- .../NativeDtmcPrctlModelCheckerTest.cpp | 39 +++++++++---------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 454c29ac1..9771e0ae1 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -146,6 +146,7 @@ namespace storm { } } + // At this point, we can be sure that the state was expanded and that we can sample according to the probabilities. // TODO: set action of topmost stack element } diff --git a/test/performance/modelchecker/GmmxxDtmcPrctModelCheckerTest.cpp b/test/performance/modelchecker/GmmxxDtmcPrctModelCheckerTest.cpp index b2a39f5dd..4284ab85c 100644 --- a/test/performance/modelchecker/GmmxxDtmcPrctModelCheckerTest.cpp +++ b/test/performance/modelchecker/GmmxxDtmcPrctModelCheckerTest.cpp @@ -8,6 +8,7 @@ #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "src/utility/solver.h" #include "src/parser/AutoParser.h" +#include "src/parser/FormulaParser.h" #include "src/models/sparse/StandardRewardModel.h" TEST(GmmxxDtmcPrctlModelCheckerTest, Crowds) { @@ -22,26 +23,26 @@ TEST(GmmxxDtmcPrctlModelCheckerTest, Crowds) { storm::modelchecker::SparseDtmcPrctlModelChecker> checker(*dtmc, std::unique_ptr>(new storm::utility::solver::GmmxxLinearEquationSolverFactory())); - auto labelFormula = std::make_shared("observe0Greater1"); - auto eventuallyFormula = std::make_shared(labelFormula); + // A parser that we use for conveniently constructing the formulas. + storm::parser::FormulaParser formulaParser; + + std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("P=? [F \"observe0Greater1\"]"); - std::unique_ptr result = checker.check(*eventuallyFormula); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.2296800237, quantitativeResult1[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("observeIGreater1"); - eventuallyFormula = std::make_shared(labelFormula); + formula = formulaParser.parseSingleFormulaFromString("P=? [F \"observeIGreater1\"]"); - result = checker.check(*eventuallyFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.05073232193, quantitativeResult2[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("observeOnlyTrueSender"); - eventuallyFormula = std::make_shared(labelFormula); + formula = formulaParser.parseSingleFormulaFromString("P=? [F \"observeOnlyTrueSender\"]"); - result = checker.check(*eventuallyFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult3 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.22742171078, quantitativeResult3[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); @@ -60,27 +61,26 @@ TEST(GmmxxDtmcPrctlModelCheckerTest, SynchronousLeader) { storm::modelchecker::SparseDtmcPrctlModelChecker> checker(*dtmc, std::unique_ptr>(new storm::utility::solver::GmmxxLinearEquationSolverFactory())); - auto labelFormula = std::make_shared("elected"); - auto eventuallyFormula = std::make_shared(labelFormula); + // A parser that we use for conveniently constructing the formulas. + storm::parser::FormulaParser formulaParser; + + std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("P=? [F \"elected\"]"); - std::unique_ptr result = checker.check(*eventuallyFormula); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1.0, quantitativeResult1[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("elected"); - auto trueFormula = std::make_shared(true); - auto boundedUntilFormula = std::make_shared(trueFormula, labelFormula, 20); + formula = formulaParser.parseSingleFormulaFromString("P=? [F<=20 \"elected\"]"); - result = checker.check(*boundedUntilFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.9993949793, quantitativeResult2[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("elected"); - auto reachabilityRewardFormula = std::make_shared(labelFormula, storm::logic::FormulaContext::Reward); + formula = formulaParser.parseSingleFormulaFromString("R=? [F \"elected\"]"); - result = checker.check(*reachabilityRewardFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult3 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1.025106273, quantitativeResult3[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); diff --git a/test/performance/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp b/test/performance/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp index 23949131a..85d6399c9 100644 --- a/test/performance/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp +++ b/test/performance/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp @@ -2,13 +2,13 @@ #include "storm-config.h" #include "src/settings/SettingsManager.h" #include "src/settings/modules/GmmxxEquationSolverSettings.h" - #include "src/settings/modules/NativeEquationSolverSettings.h" #include "src/settings/SettingMemento.h" #include "src/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "src/utility/solver.h" #include "src/parser/AutoParser.h" +#include "src/parser/FormulaParser.h" #include "src/models/sparse/StandardRewardModel.h" TEST(NativeDtmcPrctlModelCheckerTest, Crowds) { @@ -23,26 +23,26 @@ TEST(NativeDtmcPrctlModelCheckerTest, Crowds) { storm::modelchecker::SparseDtmcPrctlModelChecker> checker(*dtmc, std::unique_ptr>(new storm::utility::solver::NativeLinearEquationSolverFactory())); - auto labelFormula = std::make_shared("observe0Greater1"); - auto eventuallyFormula = std::make_shared(labelFormula); + // A parser that we use for conveniently constructing the formulas. + storm::parser::FormulaParser formulaParser; + + std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("P=? [F \"observe0Greater1\"]"); - std::unique_ptr result = checker.check(*eventuallyFormula); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.22968140721646868, quantitativeResult1[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("observeIGreater1"); - eventuallyFormula = std::make_shared(labelFormula); + formula = formulaParser.parseSingleFormulaFromString("P=? [F \"observeIGreater1\"]"); - result = checker.check(*eventuallyFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.05073232193, quantitativeResult2[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("observeOnlyTrueSender"); - eventuallyFormula = std::make_shared(labelFormula); + formula = formulaParser.parseSingleFormulaFromString("P=? [F \"observeOnlyTrueSender\"]"); - result = checker.check(*eventuallyFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult3 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.22742305378217331, quantitativeResult3[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); @@ -60,28 +60,27 @@ TEST(NativeDtmcPrctlModelCheckerTest, SynchronousLeader) { ASSERT_EQ(1574477ull, dtmc->getNumberOfTransitions()); storm::modelchecker::SparseDtmcPrctlModelChecker> checker(*dtmc, std::unique_ptr>(new storm::utility::solver::NativeLinearEquationSolverFactory())); + + // A parser that we use for conveniently constructing the formulas. + storm::parser::FormulaParser formulaParser; - auto labelFormula = std::make_shared("elected"); - auto eventuallyFormula = std::make_shared(labelFormula); + std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("P=? [F \"elected\"]"); - std::unique_ptr result = checker.check(*eventuallyFormula); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1.0, quantitativeResult1[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("elected"); - auto trueFormula = std::make_shared(true); - auto boundedUntilFormula = std::make_shared(trueFormula, labelFormula, 20); + formula = formulaParser.parseSingleFormulaFromString("P=? [F<=20 \"elected\"]"); - result = checker.check(*boundedUntilFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.9993949793, quantitativeResult2[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); - labelFormula = std::make_shared("elected"); - auto reachabilityRewardFormula = std::make_shared(labelFormula, storm::logic::FormulaContext::Reward); + formula = formulaParser.parseSingleFormulaFromString("R=? [F \"elected\"]"); - result = checker.check(*reachabilityRewardFormula); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult quantitativeResult3 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1.0252174454896057, quantitativeResult3[0], storm::settings::gmmxxEquationSolverSettings().getPrecision()); From d802f0d9c6f87986a09aa9534c5cf294ccb05be5 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 29 Mar 2016 20:26:37 +0200 Subject: [PATCH 08/31] worked a bit on the learning-based verification of MDPs Former-commit-id: bc3c0885b2ec33d344ab0ef6321f8ed91de795f9 --- src/generator/StateBehavior.cpp | 5 ++ src/generator/StateBehavior.h | 5 ++ src/modelchecker/AbstractModelChecker.cpp | 2 +- .../SparseMdpLearningModelChecker.cpp | 60 +++++++++++++------ .../SparseMdpLearningModelChecker.h | 2 + 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/generator/StateBehavior.cpp b/src/generator/StateBehavior.cpp index e14417779..6ae1f6de0 100644 --- a/src/generator/StateBehavior.cpp +++ b/src/generator/StateBehavior.cpp @@ -50,6 +50,11 @@ namespace storm { return stateRewards; } + template + std::size_t StateBehavior::getNumberOfChoices() const { + return choices.size(); + } + template class StateBehavior; template class StateBehavior; diff --git a/src/generator/StateBehavior.h b/src/generator/StateBehavior.h index 393c3280c..8f81c8b30 100644 --- a/src/generator/StateBehavior.h +++ b/src/generator/StateBehavior.h @@ -56,6 +56,11 @@ namespace storm { */ std::vector const& getStateRewards() const; + /*! + * Retrieves the number of choices in the behavior. + */ + std::size_t getNumberOfChoices() const; + private: // The choices available in the state. std::vector> choices; diff --git a/src/modelchecker/AbstractModelChecker.cpp b/src/modelchecker/AbstractModelChecker.cpp index 0ad70fd4b..850cae65d 100644 --- a/src/modelchecker/AbstractModelChecker.cpp +++ b/src/modelchecker/AbstractModelChecker.cpp @@ -13,7 +13,7 @@ namespace storm { namespace modelchecker { std::unique_ptr AbstractModelChecker::check(CheckTask const& checkTask) { storm::logic::Formula const& formula = checkTask.getFormula(); - STORM_LOG_THROW(this->canHandle(formula), storm::exceptions::InvalidArgumentException, "The model checker is not able to check the formula '" << formula << "'."); + STORM_LOG_THROW(this->canHandle(checkTask), storm::exceptions::InvalidArgumentException, "The model checker is not able to check the formula '" << formula << "'."); if (formula.isStateFormula()) { return this->checkStateFormula(checkTask.substituteFormula(formula.asStateFormula())); } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 9771e0ae1..f96308e05 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -47,6 +47,13 @@ namespace storm { upperBounds[stateToRowGroupMapping[sourceStateId]] = newUpperValue; } + template + void SparseMdpLearningModelChecker::updateProbabilitiesUsingStack(std::vector>& stateActionStack, StateType const& currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const { + while (!stateActionStack.empty()) { + updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, currentStateId, transitionMatrix, rowGroupIndices, stateToRowGroupMapping, lowerBounds, upperBounds); + } + } + template std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); @@ -62,9 +69,6 @@ namespace storm { // A container for the encountered states. storm::storage::sparse::StateStorage stateStorage(variableInformation.getTotalBitOffset(true)); - // A container that stores the states that were already expanded. - storm::storage::BitVector expandedStates; - // A generator used to explore the model. storm::generator::PrismNextStateGenerator generator(program, variableInformation, false); @@ -81,8 +85,11 @@ namespace storm { std::vector lowerBounds; std::vector upperBounds; + // A mapping of unexplored IDs to their actual compressed states. + std::unordered_map unexploredStates; + // Create a callback for the next-state generator to enable it to request the index of states. - std::function stateToIdCallback = [&stateStorage] (storm::generator::CompressedState const& state) -> StateType { + std::function stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates] (storm::generator::CompressedState const& state) -> StateType { StateType newIndex = stateStorage.numberOfStates; // Check, if the state was already registered. @@ -90,6 +97,8 @@ namespace storm { if (actualIndexBucketPair.first == newIndex) { ++stateStorage.numberOfStates; + stateToRowGroupMapping.push_back(0); + unexploredStates[newIndex] = state; } return actualIndexBucketPair.first; @@ -99,38 +108,43 @@ namespace storm { STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); // Now perform the actual sampling. - std::unordered_map unexploredStates; std::vector> stateActionStack; stateActionStack.push_back(std::make_pair(stateStorage.initialStateIndices.front(), 0)); bool foundTargetState = false; while (!foundTargetState) { StateType const& currentStateId = stateActionStack.back().first; + STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); // If the state is not yet expanded, we need to retrieve its behaviors. - if (!expandedStates.get(currentStateId)) { + auto unexploredIt = unexploredStates.find(currentStateId); + if (unexploredIt != unexploredStates.end()) { + STORM_LOG_TRACE("State was not yet expanded."); + // First, we need to get the compressed state back from the id. - auto it = unexploredStates.find(currentStateId); - STORM_LOG_ASSERT(it != unexploredStates.end(), "Unable to find unexplored state."); - storm::storage::BitVector currentState = it->second; + STORM_LOG_ASSERT(unexploredIt != unexploredStates.end(), "Unable to find unexplored state " << currentStateId << "."); + storm::storage::BitVector const& currentState = unexploredIt->second; // Before generating the behavior of the state, we need to determine whether it's a target state that // does not need to be expanded. generator.load(currentState); if (generator.satisfies(targetStateExpression)) { + STORM_LOG_TRACE("State does not need to be expanded, because it is a target state."); + // If it's in fact a goal state, we need to go backwards in the stack and update the probabilities. foundTargetState = true; stateActionStack.pop_back(); - while (!stateActionStack.empty()) { - updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBounds, upperBounds); - } - break; + STORM_LOG_TRACE("Updating probabilities along states in stack."); + updateProbabilitiesUsingStack(stateActionStack, currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBounds, upperBounds); } else { + STORM_LOG_TRACE("Expanding state."); + // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); + STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); - stateToRowGroupMapping.push_back(rowGroupIndices.size()); + stateToRowGroupMapping[currentStateId] = rowGroupIndices.size(); rowGroupIndices.push_back(matrix.size()); // Next, we insert the behavior into our matrix structure. @@ -142,13 +156,23 @@ namespace storm { } // Now that we have explored the state, we can dispose of it. - unexploredStates.erase(it); + unexploredStates.erase(unexploredIt); } } - - // At this point, we can be sure that the state was expanded and that we can sample according to the probabilities. - // TODO: set action of topmost stack element + if (!foundTargetState) { + // At this point, we can be sure that the state was expanded and that we can sample according to the + // probabilities in the matrix. + STORM_LOG_TRACE("Sampling action in state."); + uint32_t chosenAction = 0; + + STORM_LOG_TRACE("Sampling successor state according to action " << chosenAction << "."); + break; + + // TODO: set action of topmost stack element + // TOOD: determine if end component (state) + + } } return nullptr; diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index b181a44f6..f63ce40a4 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -27,6 +27,8 @@ namespace storm { private: void updateProbabilities(StateType const& sourceStateId, uint32_t action, StateType const& targetStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const; + void updateProbabilitiesUsingStack(std::vector>& stateActionStack, StateType const& currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const; + // The program that defines the model to check. storm::prism::Program program; From 034cf626a0fb230c30f3c85c5858dcc7cc8ce6ad Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 30 Mar 2016 18:28:49 +0200 Subject: [PATCH 09/31] more work on learning-based engin Former-commit-id: bbcf67abd1ce7381468984b7d6b8bec253872157 --- .../SparseMdpLearningModelChecker.cpp | 314 ++++++++++++++---- .../SparseMdpLearningModelChecker.h | 13 +- 2 files changed, 261 insertions(+), 66 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index f96308e05..d01ed5156 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -29,29 +29,124 @@ namespace storm { } template - void SparseMdpLearningModelChecker::updateProbabilities(StateType const& sourceStateId, uint32_t action, StateType const& targetStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const { + void SparseMdpLearningModelChecker::updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const { // Find out which row of the matrix we have to consider for the given action. StateType sourceRowGroup = stateToRowGroupMapping[sourceStateId]; - StateType sourceRow = sourceRowGroup + action; + StateType sourceRow = rowGroupIndices[sourceRowGroup] + action; // Compute the new lower/upper values of the action. ValueType newLowerValue = storm::utility::zero(); ValueType newUpperValue = storm::utility::zero(); + boost::optional loopProbability; for (auto const& element : transitionMatrix[sourceRow]) { - newLowerValue += element.getValue() * upperBounds[stateToRowGroupMapping[element.getColumn()]]; - newUpperValue += element.getValue() * lowerBounds[stateToRowGroupMapping[element.getColumn()]]; + // If the element is a self-loop, we treat the probability by a proper rescaling after the loop. + if (element.getColumn() == sourceStateId) { + STORM_LOG_TRACE("Found self-loop with probability " << element.getValue() << "."); + loopProbability = element.getValue(); + continue; + } + + newLowerValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::zero() : lowerBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + newUpperValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + + // If there was a self-loop with probability p, we scale the probabilities by 1/(1-p). + if (loopProbability) { + STORM_LOG_TRACE("Scaling values " << newLowerValue << " and " << newUpperValue << " with " << (storm::utility::one()/(storm::utility::one() - loopProbability.get())) << "."); + newLowerValue = newLowerValue / (storm::utility::one() - loopProbability.get()); + newUpperValue = newUpperValue / (storm::utility::one() - loopProbability.get()); } // And set them as the current value. - lowerBounds[stateToRowGroupMapping[sourceStateId]] = newLowerValue; - upperBounds[stateToRowGroupMapping[sourceStateId]] = newUpperValue; + lowerBoundsPerAction[rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action] = newLowerValue; + STORM_LOG_TRACE("Updating lower value of action " << action << " of state " << sourceStateId << " to " << newLowerValue << "."); + upperBoundsPerAction[rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action] = newUpperValue; + std::cout << "writing " << newUpperValue << " at index " << (rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action) << std::endl; + STORM_LOG_TRACE("Updating upper value of action " << action << " of state " << sourceStateId << " to " << newUpperValue << "."); + + // Check if we need to update the values for the states. + if (lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] < newLowerValue) { + lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] = newLowerValue; + STORM_LOG_TRACE("Got new lower bound for state " << sourceStateId << ": " << newLowerValue << "."); + } + if (upperBoundsPerState[stateToRowGroupMapping[sourceStateId]] > newUpperValue) { + upperBoundsPerState[stateToRowGroupMapping[sourceStateId]] = newUpperValue; + STORM_LOG_TRACE("Got new upper bound for state " << sourceStateId << ": " << newUpperValue << "."); + } + } + + template + void SparseMdpLearningModelChecker::updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const { + + std::cout << "stack:" << std::endl; + for (auto const& entry : stateActionStack) { + std::cout << entry.first << " -[" << entry.second << "]-> "; + } + std::cout << std::endl; + + while (stateActionStack.size() > 1) { + stateActionStack.pop_back(); + + updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, transitionMatrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + + } } template - void SparseMdpLearningModelChecker::updateProbabilitiesUsingStack(std::vector>& stateActionStack, StateType const& currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const { - while (!stateActionStack.empty()) { - updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, currentStateId, transitionMatrix, rowGroupIndices, stateToRowGroupMapping, lowerBounds, upperBounds); + uint32_t SparseMdpLearningModelChecker::sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& upperBoundsPerState, StateType const& unexploredMarker) { + + StateType rowGroup = stateToRowGroupMapping[currentStateId]; + STORM_LOG_TRACE("Row group for action sampling is " << rowGroup << "."); + + // First, determine all maximizing actions. + std::vector allMaxActions; + + // Determine the maximal value of any action. +// ValueType max = 0; +// for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { +// ValueType current = 0; +// for (auto const& element : transitionMatrix[row]) { +// current += element.getValue() * upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]; +// } +// +// max = std::max(max, current); +// } + + STORM_LOG_TRACE("Looking for action with value " << upperBoundsPerState[stateToRowGroupMapping[currentStateId]] << "."); + for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { + ValueType current = 0; + for (auto const& element : transitionMatrix[row]) { + std::cout << "+= " << element.getValue() << " * " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << " (col: " << element.getColumn() << " // row (grp) " << stateToRowGroupMapping[element.getColumn()] << ")" << std::endl; + current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); + + // If the action is one of the maximizing ones, insert it into our list. + // TODO: should this need to be an approximate check? + if (current == upperBoundsPerState[stateToRowGroupMapping[currentStateId]]) { + allMaxActions.push_back(row); + std::cout << "found maximizing action " << row << std::endl; + } } + + STORM_LOG_ASSERT(!allMaxActions.empty(), "Must have at least one maximizing action."); + + // Now sample from all maximizing actions. + std::uniform_int_distribution distribution(0, allMaxActions.size() - 1); + return allMaxActions[distribution(generator)] - rowGroupIndices[rowGroup]; + } + + template + typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping) { + uint32_t row = rowGroupIndices[stateToRowGroupMapping[currentStateId]]; + + // TODO: this can be precomputed. + std::vector probabilities(transitionMatrix[row].size()); + std::transform(transitionMatrix[row].begin(), transitionMatrix[row].end(), probabilities.begin(), [] (storm::storage::MatrixEntry const& entry) { return entry.getValue(); } ); + + // Now sample according to the probabilities. + std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); + return transitionMatrix[row][distribution(generator)].getColumn(); } template @@ -74,22 +169,26 @@ namespace storm { // A container that stores the transitions found so far. std::vector>> matrix; - + // A vector storing where the row group of each state starts. std::vector rowGroupIndices; + rowGroupIndices.push_back(0); // A vector storing the mapping from state ids to row groups. std::vector stateToRowGroupMapping; + StateType unexploredMarker = std::numeric_limits::max(); // Vectors to store the lower/upper bounds for each action (in each state). - std::vector lowerBounds; - std::vector upperBounds; - + std::vector lowerBoundsPerAction; + std::vector upperBoundsPerAction; + std::vector lowerBoundsPerState; + std::vector upperBoundsPerState; + // A mapping of unexplored IDs to their actual compressed states. std::unordered_map unexploredStates; // Create a callback for the next-state generator to enable it to request the index of states. - std::function stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates] (storm::generator::CompressedState const& state) -> StateType { + std::function stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates, &unexploredMarker] (storm::generator::CompressedState const& state) -> StateType { StateType newIndex = stateStorage.numberOfStates; // Check, if the state was already registered. @@ -97,84 +196,171 @@ namespace storm { if (actualIndexBucketPair.first == newIndex) { ++stateStorage.numberOfStates; - stateToRowGroupMapping.push_back(0); + stateToRowGroupMapping.push_back(unexploredMarker); unexploredStates[newIndex] = state; } return actualIndexBucketPair.first; }; - + stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback); STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); // Now perform the actual sampling. std::vector> stateActionStack; - stateActionStack.push_back(std::make_pair(stateStorage.initialStateIndices.front(), 0)); - bool foundTargetState = false; - while (!foundTargetState) { - StateType const& currentStateId = stateActionStack.back().first; - STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); - - // If the state is not yet expanded, we need to retrieve its behaviors. - auto unexploredIt = unexploredStates.find(currentStateId); - if (unexploredIt != unexploredStates.end()) { - STORM_LOG_TRACE("State was not yet expanded."); + uint_fast64_t iteration = 0; + bool convergenceCriterionMet = false; + while (!convergenceCriterionMet) { + // Start the search from the initial state. + stateActionStack.push_back(std::make_pair(stateStorage.initialStateIndices.front(), 0)); - // First, we need to get the compressed state back from the id. - STORM_LOG_ASSERT(unexploredIt != unexploredStates.end(), "Unable to find unexplored state " << currentStateId << "."); - storm::storage::BitVector const& currentState = unexploredIt->second; + bool foundTargetState = false; + while (!foundTargetState) { + StateType const& currentStateId = stateActionStack.back().first; + STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); - // Before generating the behavior of the state, we need to determine whether it's a target state that - // does not need to be expanded. - generator.load(currentState); - if (generator.satisfies(targetStateExpression)) { - STORM_LOG_TRACE("State does not need to be expanded, because it is a target state."); - - // If it's in fact a goal state, we need to go backwards in the stack and update the probabilities. - foundTargetState = true; - stateActionStack.pop_back(); + // If the state is not yet expanded, we need to retrieve its behaviors. + auto unexploredIt = unexploredStates.find(currentStateId); + if (unexploredIt != unexploredStates.end()) { + STORM_LOG_TRACE("State was not yet expanded."); - STORM_LOG_TRACE("Updating probabilities along states in stack."); - updateProbabilitiesUsingStack(stateActionStack, currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBounds, upperBounds); - } else { - STORM_LOG_TRACE("Expanding state."); - - // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. - storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); - STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); + // Map the unexplored state to a row group. + stateToRowGroupMapping[currentStateId] = rowGroupIndices.size() - 1; + STORM_LOG_TRACE("Assigning row group " << stateToRowGroupMapping[currentStateId] << " to state " << currentStateId << "."); + lowerBoundsPerState.push_back(storm::utility::zero()); + upperBoundsPerState.push_back(storm::utility::one()); - stateToRowGroupMapping[currentStateId] = rowGroupIndices.size(); - rowGroupIndices.push_back(matrix.size()); + // We need to get the compressed state back from the id to explore it. + STORM_LOG_ASSERT(unexploredIt != unexploredStates.end(), "Unable to find unexplored state " << currentStateId << "."); + storm::storage::BitVector const& currentState = unexploredIt->second; - // Next, we insert the behavior into our matrix structure. - for (auto const& choice : behavior) { + // Before generating the behavior of the state, we need to determine whether it's a target state that + // does not need to be expanded. + generator.load(currentState); + if (generator.satisfies(targetStateExpression)) { + STORM_LOG_TRACE("State does not need to be expanded, because it is a target state."); + + // If it's in fact a goal state, we need to go backwards in the stack and update the probabilities. + foundTargetState = true; + lowerBoundsPerState.back() = storm::utility::one(); + + // Set the lower/upper bounds for the only (dummy) action. + lowerBoundsPerAction.push_back(storm::utility::one()); + upperBoundsPerAction.push_back(storm::utility::one()); + + // Increase the size of the matrix, but leave the row empty. matrix.resize(matrix.size() + 1); - for (auto const& entry : choice) { - matrix.back().push_back(storm::storage::MatrixEntry(entry.first, entry.second)); + + STORM_LOG_TRACE("Updating probabilities along states in stack."); + updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + } else { + STORM_LOG_TRACE("Expanding state."); + + // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. + storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); + STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); + + // Clumsily check whether we have found a state that forms a trivial BMEC. + bool isNonacceptingTerminalState = false; + if (behavior.getNumberOfChoices() == 0) { + isNonacceptingTerminalState = true; + } else if (behavior.getNumberOfChoices() == 1) { + auto const& onlyChoice = *behavior.begin(); + if (onlyChoice.size() == 1) { + auto const& onlyEntry = *onlyChoice.begin(); + if (onlyEntry.first == currentStateId) { + isNonacceptingTerminalState = true; + } + } + } + if (isNonacceptingTerminalState) { + STORM_LOG_TRACE("State does not need to be expanded, because it forms a trivial BMEC."); + + foundTargetState = true; + upperBoundsPerState.back() = storm::utility::zero(); + + // Set the lower/upper bounds for the only (dummy) action. + lowerBoundsPerAction.push_back(storm::utility::zero()); + upperBoundsPerAction.push_back(storm::utility::zero()); + + // Increase the size of the matrix, but leave the row empty. + matrix.resize(matrix.size() + 1); + + STORM_LOG_TRACE("Updating probabilities along states in stack."); + updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + } + + // If the state was neither a trivial (non-accepting) BMEC nor a target state, we need to store + // its behavior. + if (!foundTargetState) { + // Next, we insert the behavior into our matrix structure. + matrix.resize(matrix.size() + behavior.getNumberOfChoices()); + uint32_t currentAction = 0; + for (auto const& choice : behavior) { + for (auto const& entry : choice) { + std::cout << "got " << currentStateId << " (row group " << stateToRowGroupMapping[currentStateId] << ") " << " -> " << entry.first << " with prob " << entry.second << std::endl; + matrix.back().emplace_back(entry.first, entry.second); + } + + lowerBoundsPerAction.push_back(storm::utility::zero()); + upperBoundsPerAction.push_back(storm::utility::one()); + + // Update the bounds for the explored states to initially let them have the correct value. + updateProbabilities(currentStateId, currentAction, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + + ++currentAction; + } } } // Now that we have explored the state, we can dispose of it. unexploredStates.erase(unexploredIt); + + // Terminate the row group. + rowGroupIndices.push_back(matrix.size()); + } else { + // If the state was explored before, but determined to be a terminal state of the exploration, + // we need to determine this now. + if (matrix[rowGroupIndices[stateToRowGroupMapping[currentStateId]]].empty()) { + foundTargetState = true; + } + } + + if (!foundTargetState) { + // At this point, we can be sure that the state was expanded and that we can sample according to the + // probabilities in the matrix. + uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerAction, unexploredMarker); + stateActionStack.back().second = chosenAction; + STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); + + StateType successor = sampleSuccessorFromAction(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping); + STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << "."); + + // Put the successor state and a dummy action on top of the stack. + stateActionStack.emplace_back(successor, 0); } } - if (!foundTargetState) { - // At this point, we can be sure that the state was expanded and that we can sample according to the - // probabilities in the matrix. - STORM_LOG_TRACE("Sampling action in state."); - uint32_t chosenAction = 0; + for (auto const& el : lowerBoundsPerState) { + std::cout << el << " - "; + } + std::cout << std::endl; - STORM_LOG_TRACE("Sampling successor state according to action " << chosenAction << "."); - break; - - // TODO: set action of topmost stack element - // TOOD: determine if end component (state) - + for (auto const& el : upperBoundsPerState) { + std::cout << el << " - "; } - } + std::cout << std::endl; + STORM_LOG_TRACE("Lower bound is " << lowerBoundsPerState[0] << "."); + STORM_LOG_TRACE("Upper bound is " << upperBoundsPerState[0] << "."); + ValueType difference = upperBoundsPerState[0] - lowerBoundsPerState[0]; + STORM_LOG_TRACE("Difference after iteration " << iteration << " is " << difference << "."); + convergenceCriterionMet = difference < 1e-6; + + ++iteration; + } + return nullptr; } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index f63ce40a4..90e72d85a 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -1,6 +1,8 @@ #ifndef STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ #define STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ +#include + #include "src/modelchecker/AbstractModelChecker.h" #include "src/storage/prism/Program.h" @@ -25,15 +27,22 @@ namespace storm { virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; private: - void updateProbabilities(StateType const& sourceStateId, uint32_t action, StateType const& targetStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const; + void updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; + + void updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; - void updateProbabilitiesUsingStack(std::vector>& stateActionStack, StateType const& currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds) const; + uint32_t sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& upperBounds, StateType const& unexploredMarker); + + StateType sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping); // The program that defines the model to check. storm::prism::Program program; // The variable information. storm::generator::VariableInformation variableInformation; + + // The random number generator. + std::default_random_engine generator; }; } } From 9f52d9fa97cab6f5148987ac274d263e16e16fbb Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 30 Mar 2016 21:30:02 +0200 Subject: [PATCH 10/31] first working version (for DTMCs only) Former-commit-id: d3c789596e92d09c6752081b95bbf075f560c2f4 --- .../SparseMdpLearningModelChecker.cpp | 67 ++++++++----------- .../SparseMdpLearningModelChecker.h | 2 +- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index d01ed5156..46cf53011 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -61,7 +61,6 @@ namespace storm { lowerBoundsPerAction[rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action] = newLowerValue; STORM_LOG_TRACE("Updating lower value of action " << action << " of state " << sourceStateId << " to " << newLowerValue << "."); upperBoundsPerAction[rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action] = newUpperValue; - std::cout << "writing " << newUpperValue << " at index " << (rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action) << std::endl; STORM_LOG_TRACE("Updating upper value of action " << action << " of state " << sourceStateId << " to " << newUpperValue << "."); // Check if we need to update the values for the states. @@ -78,17 +77,10 @@ namespace storm { template void SparseMdpLearningModelChecker::updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const { - std::cout << "stack:" << std::endl; - for (auto const& entry : stateActionStack) { - std::cout << entry.first << " -[" << entry.second << "]-> "; - } - std::cout << std::endl; - while (stateActionStack.size() > 1) { stateActionStack.pop_back(); updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, transitionMatrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - } } @@ -102,30 +94,31 @@ namespace storm { std::vector allMaxActions; // Determine the maximal value of any action. -// ValueType max = 0; -// for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { -// ValueType current = 0; -// for (auto const& element : transitionMatrix[row]) { -// current += element.getValue() * upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]; -// } -// -// max = std::max(max, current); -// } - - STORM_LOG_TRACE("Looking for action with value " << upperBoundsPerState[stateToRowGroupMapping[currentStateId]] << "."); + ValueType max = 0; + for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { + ValueType current = 0; + for (auto const& element : transitionMatrix[row]) { + current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + + max = std::max(max, current); + } + +// STORM_LOG_TRACE("Looking for action with value " << upperBoundsPerState[stateToRowGroupMapping[currentStateId]] << "."); + STORM_LOG_TRACE("Looking for action with value " << max << "."); + for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { ValueType current = 0; for (auto const& element : transitionMatrix[row]) { - std::cout << "+= " << element.getValue() << " * " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << " (col: " << element.getColumn() << " // row (grp) " << stateToRowGroupMapping[element.getColumn()] << ")" << std::endl; current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); } STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); // If the action is one of the maximizing ones, insert it into our list. // TODO: should this need to be an approximate check? - if (current == upperBoundsPerState[stateToRowGroupMapping[currentStateId]]) { +// if (current == upperBoundsPerState[stateToRowGroupMapping[currentStateId]]) { + if (current == max) { allMaxActions.push_back(row); - std::cout << "found maximizing action " << row << std::endl; } } @@ -146,7 +139,9 @@ namespace storm { // Now sample according to the probabilities. std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); - return transitionMatrix[row][distribution(generator)].getColumn(); + StateType offset = distribution(generator); + STORM_LOG_TRACE("Sampled " << offset << " from " << probabilities.size() << " elements."); + return transitionMatrix[row][offset].getColumn(); } template @@ -295,12 +290,12 @@ namespace storm { // its behavior. if (!foundTargetState) { // Next, we insert the behavior into our matrix structure. - matrix.resize(matrix.size() + behavior.getNumberOfChoices()); + StateType startRow = matrix.size(); + matrix.resize(startRow + behavior.getNumberOfChoices()); uint32_t currentAction = 0; for (auto const& choice : behavior) { for (auto const& entry : choice) { - std::cout << "got " << currentStateId << " (row group " << stateToRowGroupMapping[currentStateId] << ") " << " -> " << entry.first << " with prob " << entry.second << std::endl; - matrix.back().emplace_back(entry.first, entry.second); + matrix[startRow + currentAction].emplace_back(entry.first, entry.second); } lowerBoundsPerAction.push_back(storm::utility::zero()); @@ -324,6 +319,9 @@ namespace storm { // we need to determine this now. if (matrix[rowGroupIndices[stateToRowGroupMapping[currentStateId]]].empty()) { foundTargetState = true; + + // Update the bounds along the path to the terminal state. + updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); } } @@ -342,20 +340,11 @@ namespace storm { } } - for (auto const& el : lowerBoundsPerState) { - std::cout << el << " - "; - } - std::cout << std::endl; - - for (auto const& el : upperBoundsPerState) { - std::cout << el << " - "; - } - std::cout << std::endl; - - STORM_LOG_TRACE("Lower bound is " << lowerBoundsPerState[0] << "."); - STORM_LOG_TRACE("Upper bound is " << upperBoundsPerState[0] << "."); + STORM_LOG_DEBUG("Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)."); + STORM_LOG_DEBUG("Lower bound is " << lowerBoundsPerState[0] << "."); + STORM_LOG_DEBUG("Upper bound is " << upperBoundsPerState[0] << "."); ValueType difference = upperBoundsPerState[0] - lowerBoundsPerState[0]; - STORM_LOG_TRACE("Difference after iteration " << iteration << " is " << difference << "."); + STORM_LOG_DEBUG("Difference after iteration " << iteration << " is " << difference << "."); convergenceCriterionMet = difference < 1e-6; ++iteration; diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 90e72d85a..1d91a5d14 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -34,7 +34,7 @@ namespace storm { uint32_t sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& upperBounds, StateType const& unexploredMarker); StateType sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping); - + // The program that defines the model to check. storm::prism::Program program; From b06419afe0360fa08c506c9ff6dcae625d29958c Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 31 Mar 2016 18:28:30 +0200 Subject: [PATCH 11/31] working towards EC detection Former-commit-id: 78bbe54f81287fb60a5e67624fbe2cf53f303ae3 --- src/cli/entrypoints.h | 2 +- .../SparseMdpLearningModelChecker.cpp | 285 ++++++++++++------ .../SparseMdpLearningModelChecker.h | 26 +- src/parser/DeterministicModelParser.cpp | 2 +- src/parser/NondeterministicModelParser.cpp | 2 +- src/storage/SparseMatrix.cpp | 32 +- src/storage/SparseMatrix.h | 2 +- .../expressions/ToExprtkStringVisitor.cpp | 2 +- 8 files changed, 239 insertions(+), 114 deletions(-) diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index ba863af43..d14f8848c 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -64,7 +64,7 @@ namespace storm { storm::modelchecker::SparseMdpLearningModelChecker checker(program); std::unique_ptr result; if (checker.canHandle(task)) { - std::unique_ptr result = checker.check(task); + result = checker.check(task); } else { std::cout << " skipped, because the formula cannot be handled by the selected engine/method." << std::endl; } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 46cf53011..f6ecc1055 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -2,6 +2,7 @@ #include "src/storage/SparseMatrix.h" #include "src/storage/sparse/StateStorage.h" +#include "src/storage/MaximalEndComponentDecomposition.h" #include "src/generator/PrismNextStateGenerator.h" @@ -11,13 +12,16 @@ #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "src/settings/SettingsManager.h" +#include "src/settings/modules/GeneralSettings.h" + #include "src/utility/macros.h" #include "src/exceptions/NotSupportedException.h" namespace storm { namespace modelchecker { template - SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram(program)), variableInformation(this->program) { + SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram(program)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator(1e-9) { // Intentionally left empty. } @@ -104,7 +108,6 @@ namespace storm { max = std::max(max, current); } -// STORM_LOG_TRACE("Looking for action with value " << upperBoundsPerState[stateToRowGroupMapping[currentStateId]] << "."); STORM_LOG_TRACE("Looking for action with value " << max << "."); for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { @@ -115,9 +118,7 @@ namespace storm { STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); // If the action is one of the maximizing ones, insert it into our list. - // TODO: should this need to be an approximate check? -// if (current == upperBoundsPerState[stateToRowGroupMapping[currentStateId]]) { - if (current == max) { + if (comparator.isEqual(current, max)) { allMaxActions.push_back(row); } } @@ -126,99 +127,128 @@ namespace storm { // Now sample from all maximizing actions. std::uniform_int_distribution distribution(0, allMaxActions.size() - 1); - return allMaxActions[distribution(generator)] - rowGroupIndices[rowGroup]; + return allMaxActions[distribution(randomGenerator)] - rowGroupIndices[rowGroup]; } template typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping) { uint32_t row = rowGroupIndices[stateToRowGroupMapping[currentStateId]]; - // TODO: this can be precomputed. + // TODO: precompute this? std::vector probabilities(transitionMatrix[row].size()); std::transform(transitionMatrix[row].begin(), transitionMatrix[row].end(), probabilities.begin(), [] (storm::storage::MatrixEntry const& entry) { return entry.getValue(); } ); // Now sample according to the probabilities. std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); - StateType offset = distribution(generator); + StateType offset = distribution(randomGenerator); STORM_LOG_TRACE("Sampled " << offset << " from " << probabilities.size() << " elements."); return transitionMatrix[row][offset].getColumn(); } template - std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { - storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); - storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); - STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); - storm::expressions::Expression targetStateExpression; + storm::expressions::Expression SparseMdpLearningModelChecker::getTargetStateExpression(storm::logic::Formula const& subformula) { + storm::expressions::Expression result; if (subformula.isAtomicExpressionFormula()) { - targetStateExpression = subformula.asAtomicExpressionFormula().getExpression(); + result = subformula.asAtomicExpressionFormula().getExpression(); } else { - targetStateExpression = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); + result = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); } + return result; + } + + template + void SparseMdpLearningModelChecker::detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const { - // A container for the encountered states. - storm::storage::sparse::StateStorage stateStorage(variableInformation.getTotalBitOffset(true)); + // Outline: + // 1. construct a sparse transition matrix of the relevant part of the state space. + // 2. use this matrix to compute an MEC decomposition. + // 3. if non-empty analyze the decomposition for accepting/rejecting MECs. + // 4. modify matrix to account for the findings of 3. - // A generator used to explore the model. - storm::generator::PrismNextStateGenerator generator(program, variableInformation, false); + // Start with 1. + storm::storage::SparseMatrixBuilder builder(0, 0, 0, false, true, 0); + std::vector relevantStates; + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + relevantStates.push_back(state); + } + } + StateType unexploredState = relevantStates.size(); - // A container that stores the transitions found so far. - std::vector>> matrix; + std::unordered_map relevantStateToNewRowGroupMapping; + for (StateType index = 0; index < relevantStates.size(); ++index) { + relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); + } - // A vector storing where the row group of each state starts. - std::vector rowGroupIndices; - rowGroupIndices.push_back(0); + StateType currentRow = 0; + for (auto const& state : relevantStates) { + builder.newRowGroup(currentRow); + StateType rowGroup = stateToRowGroupMapping[state]; + for (auto row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { + ValueType unexpandedProbability = storm::utility::zero(); + for (auto const& entry : transitionMatrix[row]) { + auto it = relevantStateToNewRowGroupMapping.find(entry.getColumn()); + if (it != relevantStateToNewRowGroupMapping.end()) { + // If the entry is a relevant state, we copy it over (and compensate for the offset change). + builder.addNextValue(currentRow, it->second, entry.getValue()); + } else { + // If the entry is an unexpanded state, we gather the probability to later redirect it to an unexpanded sink. + unexpandedProbability += entry.getValue(); + } + } + builder.addNextValue(currentRow, unexploredState, unexpandedProbability); + ++currentRow; + } + } + builder.newRowGroup(currentRow); - // A vector storing the mapping from state ids to row groups. - std::vector stateToRowGroupMapping; - StateType unexploredMarker = std::numeric_limits::max(); - // Vectors to store the lower/upper bounds for each action (in each state). - std::vector lowerBoundsPerAction; - std::vector upperBoundsPerAction; - std::vector lowerBoundsPerState; - std::vector upperBoundsPerState; + // Go on to step 2. + storm::storage::MaximalEndComponentDecomposition mecDecomposition; - // A mapping of unexplored IDs to their actual compressed states. - std::unordered_map unexploredStates; + // 3. Analyze the MEC decomposition. - // Create a callback for the next-state generator to enable it to request the index of states. - std::function stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates, &unexploredMarker] (storm::generator::CompressedState const& state) -> StateType { - StateType newIndex = stateStorage.numberOfStates; - - // Check, if the state was already registered. - std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); - - if (actualIndexBucketPair.first == newIndex) { - ++stateStorage.numberOfStates; - stateToRowGroupMapping.push_back(unexploredMarker); - unexploredStates[newIndex] = state; - } - - return actualIndexBucketPair.first; - }; + // 4. Finally modify the system + } + + template + std::tuple::StateType, ValueType, ValueType> SparseMdpLearningModelChecker::performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage& stateStorage, storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& unexploredStates, StateType const& unexploredMarker) { + // Generate the initial state so we know where to start the simulation. stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback); STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); + StateType initialStateIndex = stateStorage.initialStateIndices.front(); + + // A set storing all target states. + boost::container::flat_set targetStates; + + // Vectors to store the lower/upper bounds for each action (in each state). + std::vector lowerBoundsPerAction; + std::vector upperBoundsPerAction; + std::vector lowerBoundsPerState; + std::vector upperBoundsPerState; // Now perform the actual sampling. std::vector> stateActionStack; - uint_fast64_t iteration = 0; + std::size_t iterations = 0; + std::size_t maxPathLength = 0; + std::size_t pathLengthUntilEndComponentDetection = 27; bool convergenceCriterionMet = false; while (!convergenceCriterionMet) { // Start the search from the initial state. - stateActionStack.push_back(std::make_pair(stateStorage.initialStateIndices.front(), 0)); - + stateActionStack.push_back(std::make_pair(initialStateIndex, 0)); + + bool foundTerminalState = false; bool foundTargetState = false; - while (!foundTargetState) { + while (!foundTerminalState) { StateType const& currentStateId = stateActionStack.back().first; STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); // If the state is not yet expanded, we need to retrieve its behaviors. auto unexploredIt = unexploredStates.find(currentStateId); if (unexploredIt != unexploredStates.end()) { - STORM_LOG_TRACE("State was not yet expanded."); + STORM_LOG_TRACE("State was not yet explored."); // Map the unexplored state to a row group. stateToRowGroupMapping[currentStateId] = rowGroupIndices.size() - 1; @@ -235,60 +265,32 @@ namespace storm { generator.load(currentState); if (generator.satisfies(targetStateExpression)) { STORM_LOG_TRACE("State does not need to be expanded, because it is a target state."); - - // If it's in fact a goal state, we need to go backwards in the stack and update the probabilities. + targetStates.insert(currentStateId); foundTargetState = true; - lowerBoundsPerState.back() = storm::utility::one(); - - // Set the lower/upper bounds for the only (dummy) action. - lowerBoundsPerAction.push_back(storm::utility::one()); - upperBoundsPerAction.push_back(storm::utility::one()); - - // Increase the size of the matrix, but leave the row empty. - matrix.resize(matrix.size() + 1); - - STORM_LOG_TRACE("Updating probabilities along states in stack."); - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + foundTerminalState = true; } else { - STORM_LOG_TRACE("Expanding state."); + STORM_LOG_TRACE("Exploring state."); // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); // Clumsily check whether we have found a state that forms a trivial BMEC. - bool isNonacceptingTerminalState = false; if (behavior.getNumberOfChoices() == 0) { - isNonacceptingTerminalState = true; + foundTerminalState = true; } else if (behavior.getNumberOfChoices() == 1) { auto const& onlyChoice = *behavior.begin(); if (onlyChoice.size() == 1) { auto const& onlyEntry = *onlyChoice.begin(); if (onlyEntry.first == currentStateId) { - isNonacceptingTerminalState = true; + foundTerminalState = true; } } } - if (isNonacceptingTerminalState) { - STORM_LOG_TRACE("State does not need to be expanded, because it forms a trivial BMEC."); - - foundTargetState = true; - upperBoundsPerState.back() = storm::utility::zero(); - - // Set the lower/upper bounds for the only (dummy) action. - lowerBoundsPerAction.push_back(storm::utility::zero()); - upperBoundsPerAction.push_back(storm::utility::zero()); - - // Increase the size of the matrix, but leave the row empty. - matrix.resize(matrix.size() + 1); - - STORM_LOG_TRACE("Updating probabilities along states in stack."); - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - } - // If the state was neither a trivial (non-accepting) BMEC nor a target state, we need to store - // its behavior. - if (!foundTargetState) { + // If the state was neither a trivial (non-accepting) terminal state nor a target state, we + // need to store its behavior. + if (!foundTerminalState) { // Next, we insert the behavior into our matrix structure. StateType startRow = matrix.size(); matrix.resize(startRow + behavior.getNumberOfChoices()); @@ -297,10 +299,10 @@ namespace storm { for (auto const& entry : choice) { matrix[startRow + currentAction].emplace_back(entry.first, entry.second); } - + lowerBoundsPerAction.push_back(storm::utility::zero()); upperBoundsPerAction.push_back(storm::utility::one()); - + // Update the bounds for the explored states to initially let them have the correct value. updateProbabilities(currentStateId, currentAction, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); @@ -309,6 +311,26 @@ namespace storm { } } + if (foundTerminalState) { + STORM_LOG_TRACE("State does not need to be explored, because it is " << (foundTargetState ? "a target state" : "a rejecting terminal state") << "."); + + if (foundTargetState) { + lowerBoundsPerState.back() = storm::utility::one(); + lowerBoundsPerAction.push_back(storm::utility::one()); + upperBoundsPerAction.push_back(storm::utility::one()); + } else { + upperBoundsPerState.back() = storm::utility::zero(); + lowerBoundsPerAction.push_back(storm::utility::zero()); + upperBoundsPerAction.push_back(storm::utility::zero()); + } + + // Increase the size of the matrix, but leave the row empty. + matrix.resize(matrix.size() + 1); + + STORM_LOG_TRACE("Updating probabilities along states in stack."); + updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + } + // Now that we have explored the state, we can dispose of it. unexploredStates.erase(unexploredIt); @@ -318,14 +340,14 @@ namespace storm { // If the state was explored before, but determined to be a terminal state of the exploration, // we need to determine this now. if (matrix[rowGroupIndices[stateToRowGroupMapping[currentStateId]]].empty()) { - foundTargetState = true; + foundTerminalState = true; // Update the bounds along the path to the terminal state. updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); } } - if (!foundTargetState) { + if (!foundTerminalState) { // At this point, we can be sure that the state was expanded and that we can sample according to the // probabilities in the matrix. uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerAction, unexploredMarker); @@ -337,20 +359,83 @@ namespace storm { // Put the successor state and a dummy action on top of the stack. stateActionStack.emplace_back(successor, 0); + maxPathLength = std::max(maxPathLength, stateActionStack.size()); + + // If the current path length exceeds the threshold, we perform an EC detection. + if (stateActionStack.size() > pathLengthUntilEndComponentDetection) { + detectEndComponents(stateActionStack, targetStates, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredStates, unexploredMarker); + } } } STORM_LOG_DEBUG("Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)."); - STORM_LOG_DEBUG("Lower bound is " << lowerBoundsPerState[0] << "."); - STORM_LOG_DEBUG("Upper bound is " << upperBoundsPerState[0] << "."); - ValueType difference = upperBoundsPerState[0] - lowerBoundsPerState[0]; - STORM_LOG_DEBUG("Difference after iteration " << iteration << " is " << difference << "."); + STORM_LOG_DEBUG("Lower bound is " << lowerBoundsPerState[initialStateIndex] << "."); + STORM_LOG_DEBUG("Upper bound is " << upperBoundsPerState[initialStateIndex] << "."); + ValueType difference = upperBoundsPerState[initialStateIndex] - lowerBoundsPerState[initialStateIndex]; + STORM_LOG_DEBUG("Difference after iteration " << iterations << " is " << difference << "."); convergenceCriterionMet = difference < 1e-6; - ++iteration; + ++iterations; } - return nullptr; + if (storm::settings::generalSettings().isShowStatisticsSet()) { + std::cout << std::endl << "Learning summary -------------------------" << std::endl; + std::cout << "Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)" << std::endl; + std::cout << "Sampling iterations: " << iterations << std::endl; + std::cout << "Maximal path length: " << maxPathLength << std::endl; + } + + return std::make_tuple(initialStateIndex, lowerBoundsPerState[initialStateIndex], upperBoundsPerState[initialStateIndex]); + } + + template + std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { + storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); + storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); + STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); + + // Derive the expression for the target states, so we know when to abort the learning run. + storm::expressions::Expression targetStateExpression = getTargetStateExpression(subformula); + + // A container for the encountered states. + storm::storage::sparse::StateStorage stateStorage(variableInformation.getTotalBitOffset(true)); + + // A generator used to explore the model. + storm::generator::PrismNextStateGenerator generator(program, variableInformation, false); + + // A container that stores the transitions found so far. + std::vector>> matrix; + + // A vector storing where the row group of each state starts. + std::vector rowGroupIndices; + rowGroupIndices.push_back(0); + + // A vector storing the mapping from state ids to row groups. + std::vector stateToRowGroupMapping; + StateType unexploredMarker = std::numeric_limits::max(); + + // A mapping of unexplored IDs to their actual compressed states. + std::unordered_map unexploredStates; + + // Create a callback for the next-state generator to enable it to request the index of states. + std::function stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates, &unexploredMarker] (storm::generator::CompressedState const& state) -> StateType { + StateType newIndex = stateStorage.numberOfStates; + + // Check, if the state was already registered. + std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); + + if (actualIndexBucketPair.first == newIndex) { + ++stateStorage.numberOfStates; + stateToRowGroupMapping.push_back(unexploredMarker); + unexploredStates[newIndex] = state; + } + + return actualIndexBucketPair.first; + }; + + // Compute and return result. + std::tuple boundsForInitialState = performLearningProcedure(targetStateExpression, stateStorage, generator, stateToIdCallback, matrix, rowGroupIndices, stateToRowGroupMapping, unexploredStates, unexploredMarker); + return std::make_unique>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); } template class SparseMdpLearningModelChecker; diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 1d91a5d14..60dc25bc2 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -10,9 +10,22 @@ #include "src/generator/CompressedState.h" #include "src/generator/VariableInformation.h" +#include "src/utility/ConstantsComparator.h" #include "src/utility/constants.h" namespace storm { + namespace storage { + namespace sparse { + template + class StateStorage; + } + } + + namespace generator { + template + class PrismNextStateGenerator; + } + namespace modelchecker { template @@ -27,6 +40,8 @@ namespace storm { virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; private: + std::tuple performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage& stateStorage, storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& unexploredStates, StateType const& unexploredMarker); + void updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; void updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; @@ -34,7 +49,11 @@ namespace storm { uint32_t sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& upperBounds, StateType const& unexploredMarker); StateType sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping); - + + void detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const; + + storm::expressions::Expression getTargetStateExpression(storm::logic::Formula const& subformula); + // The program that defines the model to check. storm::prism::Program program; @@ -42,7 +61,10 @@ namespace storm { storm::generator::VariableInformation variableInformation; // The random number generator. - std::default_random_engine generator; + std::default_random_engine randomGenerator; + + // A comparator used to determine whether values are equal. + storm::utility::ConstantsComparator comparator; }; } } diff --git a/src/parser/DeterministicModelParser.cpp b/src/parser/DeterministicModelParser.cpp index 18f14428a..2bc82362f 100644 --- a/src/parser/DeterministicModelParser.cpp +++ b/src/parser/DeterministicModelParser.cpp @@ -23,7 +23,7 @@ namespace storm { uint_fast64_t stateCount = transitions.getColumnCount(); // Parse the state labeling. - storm::models::sparse::StateLabeling labeling(std::move(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename))); + storm::models::sparse::StateLabeling labeling(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename)); // Construct the result. DeterministicModelParser::Result result(std::move(transitions), std::move(labeling)); diff --git a/src/parser/NondeterministicModelParser.cpp b/src/parser/NondeterministicModelParser.cpp index b2d539c3a..b703937fd 100644 --- a/src/parser/NondeterministicModelParser.cpp +++ b/src/parser/NondeterministicModelParser.cpp @@ -25,7 +25,7 @@ namespace storm { uint_fast64_t stateCount = transitions.getColumnCount(); // Parse the state labeling. - storm::models::sparse::StateLabeling labeling(std::move(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename))); + storm::models::sparse::StateLabeling labeling(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename)); // Only parse state rewards if a file is given. boost::optional> stateRewards; diff --git a/src/storage/SparseMatrix.cpp b/src/storage/SparseMatrix.cpp index da441214e..907f97081 100644 --- a/src/storage/SparseMatrix.cpp +++ b/src/storage/SparseMatrix.cpp @@ -106,14 +106,11 @@ namespace storm { template void SparseMatrixBuilder::addNextValue(index_type row, index_type column, ValueType const& value) { // Check that we did not move backwards wrt. the row. - if (row < lastRow) { - throw storm::exceptions::InvalidArgumentException() << "Illegal call to SparseMatrixBuilder::addNextValue: adding an element in row " << row << ", but an element in row " << lastRow << " has already been added."; - } + STORM_LOG_THROW(row >= lastRow, storm::exceptions::InvalidArgumentException, "Adding an element in row " << row << ", but an element in row " << lastRow << " has already been added."); - // Check that we did not move backwards wrt. to column. - if (row == lastRow && column < lastColumn) { - throw storm::exceptions::InvalidArgumentException() << "Illegal call to SparseMatrixBuilder::addNextValue: adding an element in column " << column << " in row " << row << ", but an element in column " << lastColumn << " has already been added in that row."; - } + // If the element is in the same row, but was not inserted in the correct order, we need to fix the row after + // the insertion. + bool fixCurrentRow = row == lastRow && column < lastColumn; // If we switched to another row, we have to adjust the missing entries in the row indices vector. if (row != lastRow) { @@ -132,6 +129,27 @@ namespace storm { highestColumn = std::max(highestColumn, column); ++currentEntryCount; + // If we need to fix the row, do so now. + if (fixCurrentRow) { + // First, we sort according to columns. + std::sort(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry const& a, storm::storage::MatrixEntry const& b) { + return a.getColumn() < b.getColumn(); + }); + + // Then, we eliminate possible duplicate entries. + auto it = std::unique(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry const& a, storm::storage::MatrixEntry const& b) { + return a.getColumn() == b.getColumn(); + }); + + // Finally, remove the superfluous elements. + std::size_t elementsToRemove = std::distance(it, columnsAndValues.end()); + if (elementsToRemove > 0) { + STORM_LOG_WARN("Unordered insertion into matrix builder caused duplicate entries."); + currentEntryCount -= elementsToRemove; + columnsAndValues.resize(columnsAndValues.size() - elementsToRemove); + } + } + // In case we did not expect this value, we throw an exception. if (forceInitialDimensions) { STORM_LOG_THROW(!initialRowCountSet || lastRow < initialRowCount, storm::exceptions::OutOfRangeException, "Cannot insert value at illegal row " << lastRow << "."); diff --git a/src/storage/SparseMatrix.h b/src/storage/SparseMatrix.h index c5c7133c6..f1debced1 100644 --- a/src/storage/SparseMatrix.h +++ b/src/storage/SparseMatrix.h @@ -229,7 +229,7 @@ namespace storm { * @return True if replacement took place, False if nothing changed. */ bool replaceColumns(std::vector const& replacements, index_type offset); - + private: // A flag indicating whether a row count was set upon construction. bool initialRowCountSet; diff --git a/src/storage/expressions/ToExprtkStringVisitor.cpp b/src/storage/expressions/ToExprtkStringVisitor.cpp index f3a89b2c5..c0cea43ac 100644 --- a/src/storage/expressions/ToExprtkStringVisitor.cpp +++ b/src/storage/expressions/ToExprtkStringVisitor.cpp @@ -10,7 +10,7 @@ namespace storm { stream.str(""); stream.clear(); expression->accept(*this); - return std::move(stream.str()); + return stream.str(); } boost::any ToExprtkStringVisitor::visit(IfThenElseExpression const& expression) { From e4a5c1d0d6bce0cf6115aa880a63171bd37cec19 Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 1 Apr 2016 16:01:01 +0200 Subject: [PATCH 12/31] more work on EC detection (again0 more work on EC detection (again) Former-commit-id: 1b618f45ec697039e55283c4dea350d0368f0fa9 --- .../SparseMdpLearningModelChecker.cpp | 48 ++++++++++++++++--- .../SparseMdpLearningModelChecker.h | 2 +- .../MaximalEndComponentDecomposition.cpp | 6 ++- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index f6ecc1055..ed51aa0fa 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -12,6 +12,8 @@ #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "src/models/sparse/StandardRewardModel.h" + #include "src/settings/SettingsManager.h" #include "src/settings/modules/GeneralSettings.h" @@ -157,13 +159,14 @@ namespace storm { } template - void SparseMdpLearningModelChecker::detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const { + void SparseMdpLearningModelChecker::detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const { + + STORM_LOG_TRACE("Starting EC detection."); // Outline: // 1. construct a sparse transition matrix of the relevant part of the state space. // 2. use this matrix to compute an MEC decomposition. // 3. if non-empty analyze the decomposition for accepting/rejecting MECs. - // 4. modify matrix to account for the findings of 3. // Start with 1. storm::storage::SparseMatrixBuilder builder(0, 0, 0, false, true, 0); @@ -196,19 +199,52 @@ namespace storm { unexpandedProbability += entry.getValue(); } } - builder.addNextValue(currentRow, unexploredState, unexpandedProbability); + if (unexpandedProbability != storm::utility::zero()) { + builder.addNextValue(currentRow, unexploredState, unexpandedProbability); + } ++currentRow; } } + // Finally, make the unexpanded state absorbing. builder.newRowGroup(currentRow); - + builder.addNextValue(currentRow, unexploredState, storm::utility::one()); + STORM_LOG_TRACE("Successfully built matrix for MEC decomposition."); // Go on to step 2. - storm::storage::MaximalEndComponentDecomposition mecDecomposition; + storm::storage::SparseMatrix relevantStatesMatrix = builder.build(); + storm::storage::MaximalEndComponentDecomposition mecDecomposition(relevantStatesMatrix, relevantStatesMatrix.transpose(true)); + STORM_LOG_TRACE("Successfully computed MEC decomposition. Found " << (mecDecomposition.size() > 1 ? (mecDecomposition.size() - 1) : 0) << " MEC(s)."); // 3. Analyze the MEC decomposition. + std::unordered_map> stateToOutgoingChoices; + for (auto const& mec : mecDecomposition) { + // Ignore the (expected) MEC of the unexplored state. + if (mec.containsState(unexploredState)) { + continue; + } + + // Now we delete all choices that belong to the MEC. + for (auto const& stateAndChoices : mec) { + // Compute the state of the original model that corresponds to the current state. + StateType originalState = stateToRowGroupMapping[relevantStates[relevantStateToNewRowGroupMapping.at(stateAndChoices.first)]]; + + auto includedChoicesIt = stateAndChoices.second.begin(); + auto includedChoicesIte = stateAndChoices.second.end(); + + // Now iterate through all the choices and delete the ones included in the MEC and record outgoing + // choices (to make them available in the other states of the EC). + for (auto choice = rowGroupIndices[originalState]; choice < rowGroupIndices[originalState + 1]; ++choice) { + if (includedChoicesIt != includedChoicesIte && choice - rowGroupIndices[originalState] == *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { + transitionMatrix[choice].clear(); + transitionMatrix[choice].shrink_to_fit(); + } else { + + } + } + } + } - // 4. Finally modify the system + exit(-1); } template diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 60dc25bc2..9d4820b51 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -50,7 +50,7 @@ namespace storm { StateType sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping); - void detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const; + void detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const; storm::expressions::Expression getTargetStateExpression(storm::logic::Formula const& subformula); diff --git a/src/storage/MaximalEndComponentDecomposition.cpp b/src/storage/MaximalEndComponentDecomposition.cpp index 31c421224..87c4bc509 100644 --- a/src/storage/MaximalEndComponentDecomposition.cpp +++ b/src/storage/MaximalEndComponentDecomposition.cpp @@ -74,7 +74,7 @@ namespace storm { // from iterator to const_iterator only for "set, multiset, map [and] multimap". for (std::list::const_iterator mecIterator = endComponentStateSets.begin(); mecIterator != endComponentStateSets.end();) { StateBlock const& mec = *mecIterator; - + // Keep track of whether the MEC changed during this iteration. bool mecChanged = false; @@ -98,6 +98,10 @@ namespace storm { for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { bool choiceContainedInMEC = true; for (auto const& entry : transitionMatrix.getRow(choice)) { + if (entry.getValue() != storm::utility::zero()) { + continue; + } + if (!scc.containsState(entry.getColumn())) { choiceContainedInMEC = false; break; From 38ea181e3d4b90d094294669bb3943a149ba346c Mon Sep 17 00:00:00 2001 From: dehnert Date: Sun, 3 Apr 2016 20:45:31 +0200 Subject: [PATCH 13/31] added tons of debug output. all small test models now show sane results Former-commit-id: ecfa5ce433aaab679a9747aba8102310a3aef281 --- src/cli/entrypoints.h | 2 +- .../SparseMdpLearningModelChecker.cpp | 388 ++++++++++++++---- .../SparseMdpLearningModelChecker.h | 14 +- .../MaximalEndComponentDecomposition.cpp | 3 +- 4 files changed, 315 insertions(+), 92 deletions(-) diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index d14f8848c..505ec0cd3 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -61,7 +61,7 @@ namespace storm { STORM_LOG_THROW(program.getModelType() == storm::prism::Program::ModelType::DTMC || program.getModelType() == storm::prism::Program::ModelType::MDP, storm::exceptions::InvalidSettingsException, "Currently learning-based verification is only available for DTMCs and MDPs."); std::cout << std::endl << "Model checking property: " << *formula << " ..."; storm::modelchecker::CheckTask task(*formula, onlyInitialStatesRelevant); - storm::modelchecker::SparseMdpLearningModelChecker checker(program); + storm::modelchecker::SparseMdpLearningModelChecker checker(program, storm::utility::prism::parseConstantDefinitionString(program, storm::settings::generalSettings().getConstantDefinitionString())); std::unique_ptr result; if (checker.canHandle(task)) { result = checker.check(task); diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index ed51aa0fa..aeac6776a 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -23,7 +23,8 @@ namespace storm { namespace modelchecker { template - SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram(program)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator(1e-9) { + SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions) : program(storm::utility::prism::preprocessProgram(program, constantDefinitions)), variableInformation(this->program), //randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), + comparator(1e-9) { // Intentionally left empty. } @@ -37,91 +38,199 @@ namespace storm { template void SparseMdpLearningModelChecker::updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const { // Find out which row of the matrix we have to consider for the given action. - StateType sourceRowGroup = stateToRowGroupMapping[sourceStateId]; - StateType sourceRow = rowGroupIndices[sourceRowGroup] + action; + StateType sourceRow = action; // Compute the new lower/upper values of the action. ValueType newLowerValue = storm::utility::zero(); ValueType newUpperValue = storm::utility::zero(); - boost::optional loopProbability; + +// boost::optional loopProbability; for (auto const& element : transitionMatrix[sourceRow]) { // If the element is a self-loop, we treat the probability by a proper rescaling after the loop. - if (element.getColumn() == sourceStateId) { - STORM_LOG_TRACE("Found self-loop with probability " << element.getValue() << "."); - loopProbability = element.getValue(); - continue; - } +// if (element.getColumn() == sourceStateId) { +// STORM_LOG_TRACE("Found self-loop with probability " << element.getValue() << "."); +// loopProbability = element.getValue(); +// continue; +// } + std::cout << "lower += " << element.getValue() << " * state[" << element.getColumn() << "] = " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::zero() : lowerBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << std::endl; + if (stateToRowGroupMapping[element.getColumn()] != unexploredMarker) { + std::cout << "upper bounds per state @ " << stateToRowGroupMapping[element.getColumn()] << " is " << upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]] << std::endl; + } + std::cout << "upper += " << element.getValue() << " * state[" << element.getColumn() << "] = " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << std::endl; newLowerValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::zero() : lowerBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); newUpperValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + std::cout << "after iter " << newLowerValue << " and " << newUpperValue << std::endl; } // If there was a self-loop with probability p, we scale the probabilities by 1/(1-p). - if (loopProbability) { - STORM_LOG_TRACE("Scaling values " << newLowerValue << " and " << newUpperValue << " with " << (storm::utility::one()/(storm::utility::one() - loopProbability.get())) << "."); - newLowerValue = newLowerValue / (storm::utility::one() - loopProbability.get()); - newUpperValue = newUpperValue / (storm::utility::one() - loopProbability.get()); - } +// if (loopProbability) { +// STORM_LOG_TRACE("Scaling values " << newLowerValue << " and " << newUpperValue << " with " << (storm::utility::one()/(storm::utility::one() - loopProbability.get())) << "."); +// newLowerValue = newLowerValue / (storm::utility::one() - loopProbability.get()); +// newUpperValue = newUpperValue / (storm::utility::one() - loopProbability.get()); +// } // And set them as the current value. - lowerBoundsPerAction[rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action] = newLowerValue; - STORM_LOG_TRACE("Updating lower value of action " << action << " of state " << sourceStateId << " to " << newLowerValue << "."); - upperBoundsPerAction[rowGroupIndices[stateToRowGroupMapping[sourceStateId]] + action] = newUpperValue; - STORM_LOG_TRACE("Updating upper value of action " << action << " of state " << sourceStateId << " to " << newUpperValue << "."); + std::cout << newLowerValue << " vs " << newUpperValue << std::endl; + STORM_LOG_ASSERT(newLowerValue <= newUpperValue, "Lower bound must always be smaller or equal than upper bound."); + STORM_LOG_TRACE("Updating lower value of action " << action << " of state " << sourceStateId << " to " << newLowerValue << " (was " << lowerBoundsPerAction[action] << ")."); + lowerBoundsPerAction[action] = newLowerValue; + STORM_LOG_TRACE("Updating upper value of action " << action << " of state " << sourceStateId << " to " << newUpperValue << " (was " << upperBoundsPerAction[action] << ")."); + upperBoundsPerAction[action] = newUpperValue; // Check if we need to update the values for the states. if (lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] < newLowerValue) { + STORM_LOG_TRACE("Got new lower bound for state " << sourceStateId << ": " << newLowerValue << " (was " << lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] << ")."); lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] = newLowerValue; - STORM_LOG_TRACE("Got new lower bound for state " << sourceStateId << ": " << newLowerValue << "."); } - if (upperBoundsPerState[stateToRowGroupMapping[sourceStateId]] > newUpperValue) { - upperBoundsPerState[stateToRowGroupMapping[sourceStateId]] = newUpperValue; - STORM_LOG_TRACE("Got new upper bound for state " << sourceStateId << ": " << newUpperValue << "."); + + uint32_t sourceRowGroup = stateToRowGroupMapping[sourceStateId]; + if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { + if (rowGroupIndices[sourceRowGroup + 1] - rowGroupIndices[sourceRowGroup] > 1) { + ValueType max = storm::utility::zero(); + + for (uint32_t currentAction = rowGroupIndices[sourceRowGroup]; currentAction < rowGroupIndices[sourceRowGroup + 1]; ++currentAction) { + std::cout << "cur: " << currentAction << std::endl; + if (currentAction == action) { + continue; + } + + ValueType currentValue = storm::utility::zero(); + for (auto const& element : transitionMatrix[currentAction]) { + currentValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + max = std::max(max, currentValue); + std::cout << "max is " << max << std::endl; + } + + newUpperValue = std::max(newUpperValue, max); + } + + if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { + STORM_LOG_TRACE("Got new upper bound for state " << sourceStateId << ": " << newUpperValue << " (was " << upperBoundsPerState[sourceRowGroup] << ")."); + std::cout << "writing at index " << sourceRowGroup << std::endl; + upperBoundsPerState[sourceRowGroup] = newUpperValue; + } } } template void SparseMdpLearningModelChecker::updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const { - while (stateActionStack.size() > 1) { - stateActionStack.pop_back(); - + std::cout << "stack is:" << std::endl; + for (auto const& el : stateActionStack) { + std::cout << el.first << "-[" << el.second << "]-> "; + } + std::cout << std::endl; + + stateActionStack.pop_back(); + while (!stateActionStack.empty()) { updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, transitionMatrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + stateActionStack.pop_back(); + } + } + + template + std::pair SparseMdpLearningModelChecker::computeValuesOfChoice(uint32_t action, std::vector>> const& transitionMatrix, std::vector const& stateToRowGroupMapping, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker) { + std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + for (auto const& element : transitionMatrix[action]) { + result.first += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::zero() : lowerBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + result.second += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + return result; + } + + template + std::pair SparseMdpLearningModelChecker::computeValuesOfState(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& lowerBounds, std::vector const& upperBounds, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker) { + StateType sourceRowGroup = stateToRowGroupMapping[currentStateId]; + std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + for (uint32_t choice = rowGroupIndices[sourceRowGroup]; choice < rowGroupIndices[sourceRowGroup + 1]; ++choice) { + std::pair choiceValues = computeValuesOfChoice(choice, transitionMatrix, stateToRowGroupMapping, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + result.first = std::max(choiceValues.first, result.first); + result.second = std::max(choiceValues.second, result.second); } + return result; } template - uint32_t SparseMdpLearningModelChecker::sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& upperBoundsPerState, StateType const& unexploredMarker) { + uint32_t SparseMdpLearningModelChecker::sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& upperBoundsPerState, std::unordered_map const& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker) { StateType rowGroup = stateToRowGroupMapping[currentStateId]; - STORM_LOG_TRACE("Row group for action sampling is " << rowGroup << "."); // First, determine all maximizing actions. std::vector allMaxActions; + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [x, " << upperBoundsPerState[stateToRowGroupMapping[state]] << "]" << std::endl; + } else { + std::cout << "state " << state << " is unexplored" << std::endl; + } + } + // Determine the maximal value of any action. ValueType max = 0; - for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { - ValueType current = 0; - for (auto const& element : transitionMatrix[row]) { - current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + auto choicesInEcIt = stateToLeavingChoicesOfEndComponent.find(currentStateId); + if (choicesInEcIt != stateToLeavingChoicesOfEndComponent.end()) { + STORM_LOG_TRACE("Sampling from actions leaving the previously detected EC."); + for (auto const& row : *choicesInEcIt->second) { + ValueType current = 0; + for (auto const& element : transitionMatrix[row]) { + current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + + max = std::max(max, current); + } + } else { + STORM_LOG_TRACE("Sampling from actions leaving the state."); + for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { + ValueType current = 0; + for (auto const& element : transitionMatrix[row]) { + current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + max = std::max(max, current); } - - max = std::max(max, current); } STORM_LOG_TRACE("Looking for action with value " << max << "."); - for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { - ValueType current = 0; - for (auto const& element : transitionMatrix[row]) { - current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [x, " << upperBoundsPerState[stateToRowGroupMapping[state]] << "]" << std::endl; + } else { + std::cout << "state " << state << " is unexplored" << std::endl; } - STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); - - // If the action is one of the maximizing ones, insert it into our list. - if (comparator.isEqual(current, max)) { - allMaxActions.push_back(row); + } + + if (choicesInEcIt != stateToLeavingChoicesOfEndComponent.end()) { + for (auto const& row : *choicesInEcIt->second) { + ValueType current = 0; + for (auto const& element : transitionMatrix[row]) { + current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); + + // If the action is one of the maximizing ones, insert it into our list. + if (comparator.isEqual(current, max)) { + allMaxActions.push_back(row); + } + } + } else { + for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { + ValueType current = 0; + for (auto const& element : transitionMatrix[row]) { + if (stateToRowGroupMapping[element.getColumn()] != unexploredMarker) { + std::cout << "upper bounds per state @ " << stateToRowGroupMapping[element.getColumn()] << " is " << upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]] << std::endl; + } + std::cout << "+= " << element.getValue() << " * " << "state[" << element.getColumn() << "] = " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << std::endl; + current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); + + // If the action is one of the maximizing ones, insert it into our list. + if (comparator.isEqual(current, max)) { + allMaxActions.push_back(row); + } } } @@ -129,12 +238,12 @@ namespace storm { // Now sample from all maximizing actions. std::uniform_int_distribution distribution(0, allMaxActions.size() - 1); - return allMaxActions[distribution(randomGenerator)] - rowGroupIndices[rowGroup]; + return allMaxActions[distribution(randomGenerator)]; } template - typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping) { - uint32_t row = rowGroupIndices[stateToRowGroupMapping[currentStateId]]; + typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(StateType chosenAction, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping) { + uint32_t row = chosenAction; // TODO: precompute this? std::vector probabilities(transitionMatrix[row].size()); @@ -143,7 +252,6 @@ namespace storm { // Now sample according to the probabilities. std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); StateType offset = distribution(randomGenerator); - STORM_LOG_TRACE("Sampled " << offset << " from " << probabilities.size() << " elements."); return transitionMatrix[row][offset].getColumn(); } @@ -159,7 +267,7 @@ namespace storm { } template - void SparseMdpLearningModelChecker::detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const { + void SparseMdpLearningModelChecker::detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set& terminalStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker) const { STORM_LOG_TRACE("Starting EC detection."); @@ -170,19 +278,26 @@ namespace storm { // Start with 1. storm::storage::SparseMatrixBuilder builder(0, 0, 0, false, true, 0); + + // Determine the set of states that was expanded. std::vector relevantStates; for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { if (stateToRowGroupMapping[state] != unexploredMarker) { relevantStates.push_back(state); } } + // Sort according to the actual row groups so we can insert the elements in order later. + std::sort(relevantStates.begin(), relevantStates.end(), [&stateToRowGroupMapping] (StateType const& a, StateType const& b) { return stateToRowGroupMapping[a] < stateToRowGroupMapping[b]; }); StateType unexploredState = relevantStates.size(); + // Create a mapping for faster look-up during the translation of flexible matrix to the real sparse matrix. std::unordered_map relevantStateToNewRowGroupMapping; for (StateType index = 0; index < relevantStates.size(); ++index) { + std::cout << "relevant: " << relevantStates[index] << std::endl; relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); } + // Do the actual translation. StateType currentRow = 0; for (auto const& state : relevantStates) { builder.newRowGroup(currentRow); @@ -205,7 +320,7 @@ namespace storm { ++currentRow; } } - // Finally, make the unexpanded state absorbing. + // Then, make the unexpanded state absorbing. builder.newRowGroup(currentRow); builder.addNextValue(currentRow, unexploredState, storm::utility::one()); STORM_LOG_TRACE("Successfully built matrix for MEC decomposition."); @@ -216,35 +331,76 @@ namespace storm { STORM_LOG_TRACE("Successfully computed MEC decomposition. Found " << (mecDecomposition.size() > 1 ? (mecDecomposition.size() - 1) : 0) << " MEC(s)."); // 3. Analyze the MEC decomposition. - std::unordered_map> stateToOutgoingChoices; for (auto const& mec : mecDecomposition) { // Ignore the (expected) MEC of the unexplored state. if (mec.containsState(unexploredState)) { continue; } - // Now we delete all choices that belong to the MEC. + bool containsTargetState = false; + + // Now we record all choices leaving the EC. + ChoiceSetPointer leavingChoices = std::make_shared(); for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. - StateType originalState = stateToRowGroupMapping[relevantStates[relevantStateToNewRowGroupMapping.at(stateAndChoices.first)]]; + std::cout << "local state: " << stateAndChoices.first << std::endl; + StateType originalState = relevantStates[stateAndChoices.first]; + std::cout << "original state: " << originalState << std::endl; + uint32_t originalRowGroup = stateToRowGroupMapping[originalState]; + std::cout << "original row group: " << originalRowGroup << std::endl; + + // TODO: This check for a target state is a bit hackish and only works for max probabilities. + if (!containsTargetState && lowerBoundsPerState[originalRowGroup] == storm::utility::one()) { + containsTargetState = true; + } auto includedChoicesIt = stateAndChoices.second.begin(); auto includedChoicesIte = stateAndChoices.second.end(); - // Now iterate through all the choices and delete the ones included in the MEC and record outgoing - // choices (to make them available in the other states of the EC). - for (auto choice = rowGroupIndices[originalState]; choice < rowGroupIndices[originalState + 1]; ++choice) { - if (includedChoicesIt != includedChoicesIte && choice - rowGroupIndices[originalState] == *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { - transitionMatrix[choice].clear(); - transitionMatrix[choice].shrink_to_fit(); + for (auto choice = rowGroupIndices[originalRowGroup]; choice < rowGroupIndices[originalRowGroup + 1]; ++choice) { + if (includedChoicesIt != includedChoicesIte) { + STORM_LOG_TRACE("Next (local) choice contained in MEC is " << (*includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first])); + STORM_LOG_TRACE("Current (local) choice iterated is " << (choice - rowGroupIndices[originalRowGroup])); + if (choice - rowGroupIndices[originalRowGroup] != *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { + STORM_LOG_TRACE("Choice leaves the EC."); + leavingChoices->insert(choice); + } else { + STORM_LOG_TRACE("Choice stays in the EC."); + ++includedChoicesIt; + } } else { - + STORM_LOG_TRACE("Choice leaves the EC, because there is no more choice staying in the EC."); + leavingChoices->insert(choice); } } + + stateToLeavingChoicesOfEndComponent[originalState] = leavingChoices; + } + + // If one of the states of the EC is a target state, all states in the EC have probability 1. + if (containsTargetState) { + STORM_LOG_TRACE("MEC contains a target state."); + for (auto const& stateAndChoices : mec) { + // Compute the state of the original model that corresponds to the current state. + StateType originalState = relevantStates[stateAndChoices.first]; + + STORM_LOG_TRACE("Setting lower bound of state in row group " << stateToRowGroupMapping[originalState] << " to 1."); + lowerBoundsPerState[stateToRowGroupMapping[originalState]] = storm::utility::one(); + terminalStates.insert(originalState); + } + } else if (leavingChoices->empty()) { + STORM_LOG_TRACE("MEC's leaving choices are empty."); + // If there is no choice leaving the EC, but it contains no target state, all states have probability 0. + for (auto const& stateAndChoices : mec) { + // Compute the state of the original model that corresponds to the current state. + StateType originalState = relevantStates[stateAndChoices.first]; + + STORM_LOG_TRACE("Setting upper bound of state in row group " << stateToRowGroupMapping[originalState] << " to 0."); + upperBoundsPerState[stateToRowGroupMapping[originalState]] = storm::utility::zero(); + terminalStates.insert(originalState); + } } } - - exit(-1); } template @@ -255,8 +411,8 @@ namespace storm { STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); StateType initialStateIndex = stateStorage.initialStateIndices.front(); - // A set storing all target states. - boost::container::flat_set targetStates; + // A set storing all states in which to terminate the search. + boost::container::flat_set terminalStates; // Vectors to store the lower/upper bounds for each action (in each state). std::vector lowerBoundsPerAction; @@ -264,11 +420,16 @@ namespace storm { std::vector lowerBoundsPerState; std::vector upperBoundsPerState; + // Since we might run into end-components, we track a mapping from states in ECs to all leaving choices of + // that EC. + std::unordered_map stateToLeavingChoicesOfEndComponent; + // Now perform the actual sampling. std::vector> stateActionStack; std::size_t iterations = 0; std::size_t maxPathLength = 0; + std::size_t numberOfTargetStates = 0; std::size_t pathLengthUntilEndComponentDetection = 27; bool convergenceCriterionMet = false; while (!convergenceCriterionMet) { @@ -300,8 +461,8 @@ namespace storm { // does not need to be expanded. generator.load(currentState); if (generator.satisfies(targetStateExpression)) { - STORM_LOG_TRACE("State does not need to be expanded, because it is a target state."); - targetStates.insert(currentStateId); + STORM_LOG_TRACE("State does not need to be expanded, because it is a target state. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + ++numberOfTargetStates; foundTargetState = true; foundTerminalState = true; } else { @@ -330,25 +491,40 @@ namespace storm { // Next, we insert the behavior into our matrix structure. StateType startRow = matrix.size(); matrix.resize(startRow + behavior.getNumberOfChoices()); + + // Terminate the row group. + rowGroupIndices.push_back(matrix.size()); + uint32_t currentAction = 0; for (auto const& choice : behavior) { for (auto const& entry : choice) { + std::cout << "adding " << currentStateId << " -> " << entry.first << " with prob " << entry.second << std::endl; matrix[startRow + currentAction].emplace_back(entry.first, entry.second); } lowerBoundsPerAction.push_back(storm::utility::zero()); upperBoundsPerAction.push_back(storm::utility::one()); - // Update the bounds for the explored states to initially let them have the correct value. - updateProbabilities(currentStateId, currentAction, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + std::pair values = computeValuesOfChoice(startRow + currentAction, matrix, stateToRowGroupMapping, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + lowerBoundsPerAction.back() = values.first; + upperBoundsPerAction.back() = values.second; + + STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << lowerBoundsPerAction.back() << " and " << upperBoundsPerAction.back() << "."); ++currentAction; } + + std::pair values = computeValuesOfState(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + lowerBoundsPerState.back() = values.first; + upperBoundsPerState.back() = values.second; + + STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << lowerBoundsPerState.back() << " and " << upperBoundsPerState.back() << "."); } } if (foundTerminalState) { STORM_LOG_TRACE("State does not need to be explored, because it is " << (foundTargetState ? "a target state" : "a rejecting terminal state") << "."); + terminalStates.insert(currentStateId); if (foundTargetState) { lowerBoundsPerState.back() = storm::utility::one(); @@ -363,50 +539,90 @@ namespace storm { // Increase the size of the matrix, but leave the row empty. matrix.resize(matrix.size() + 1); - STORM_LOG_TRACE("Updating probabilities along states in stack."); - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + // Terminate the row group. + rowGroupIndices.push_back(matrix.size()); } // Now that we have explored the state, we can dispose of it. unexploredStates.erase(unexploredIt); - - // Terminate the row group. - rowGroupIndices.push_back(matrix.size()); } else { - // If the state was explored before, but determined to be a terminal state of the exploration, - // we need to determine this now. - if (matrix[rowGroupIndices[stateToRowGroupMapping[currentStateId]]].empty()) { + if (terminalStates.find(currentStateId) != terminalStates.end()) { + STORM_LOG_TRACE("Found already explored terminal state: " << currentStateId << "."); foundTerminalState = true; - - // Update the bounds along the path to the terminal state. - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); } } - if (!foundTerminalState) { + if (foundTerminalState) { + // Update the bounds along the path to the terminal state. + STORM_LOG_TRACE("Found terminal state, updating probabilities along path."); + updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + } else { + std::cout << "(2) stack is:" << std::endl; + for (auto const& el : stateActionStack) { + std::cout << el.first << "-[" << el.second << "]-> "; + } + std::cout << std::endl; + + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; + for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { + std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; + } + std::cout << std::endl; + } else { + std::cout << "state " << state << " is unexplored" << std::endl; + } + } + // At this point, we can be sure that the state was expanded and that we can sample according to the // probabilities in the matrix. - uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerAction, unexploredMarker); + uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); stateActionStack.back().second = chosenAction; STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); - StateType successor = sampleSuccessorFromAction(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping); + StateType successor = sampleSuccessorFromAction(chosenAction, matrix, rowGroupIndices, stateToRowGroupMapping); STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << "."); // Put the successor state and a dummy action on top of the stack. stateActionStack.emplace_back(successor, 0); maxPathLength = std::max(maxPathLength, stateActionStack.size()); - // If the current path length exceeds the threshold, we perform an EC detection. - if (stateActionStack.size() > pathLengthUntilEndComponentDetection) { - detectEndComponents(stateActionStack, targetStates, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredStates, unexploredMarker); + // If the current path length exceeds the threshold and the model is a nondeterministic one, we + // perform an EC detection. + if (stateActionStack.size() > pathLengthUntilEndComponentDetection && !program.isDeterministicModel()) { + detectEndComponents(stateActionStack, terminalStates, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); + + // Abort the current search. + STORM_LOG_TRACE("Aborting the search after EC detection."); + stateActionStack.clear(); + break; } } } + // Sanity check of results. + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + STORM_LOG_ASSERT(lowerBoundsPerState[stateToRowGroupMapping[state]] <= upperBoundsPerState[stateToRowGroupMapping[state]], "The bounds for state " << state << " are not in a sane relation: " << lowerBoundsPerState[stateToRowGroupMapping[state]] << " > " << upperBoundsPerState[stateToRowGroupMapping[state]] << "."); + } + } + + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; + for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { + std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; + } + std::cout << std::endl; + } else { + std::cout << "state " << state << " is unexplored" << std::endl; + } + } + + STORM_LOG_DEBUG("Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)."); - STORM_LOG_DEBUG("Lower bound is " << lowerBoundsPerState[initialStateIndex] << "."); - STORM_LOG_DEBUG("Upper bound is " << upperBoundsPerState[initialStateIndex] << "."); + STORM_LOG_DEBUG("Value of initial state is in [" << lowerBoundsPerState[initialStateIndex] << ", " << upperBoundsPerState[initialStateIndex] << "]."); ValueType difference = upperBoundsPerState[initialStateIndex] - lowerBoundsPerState[initialStateIndex]; STORM_LOG_DEBUG("Difference after iteration " << iterations << " is " << difference << "."); convergenceCriterionMet = difference < 1e-6; @@ -416,7 +632,7 @@ namespace storm { if (storm::settings::generalSettings().isShowStatisticsSet()) { std::cout << std::endl << "Learning summary -------------------------" << std::endl; - std::cout << "Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)" << std::endl; + std::cout << "Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored, " << numberOfTargetStates << " target states)" << std::endl; std::cout << "Sampling iterations: " << iterations << std::endl; std::cout << "Maximal path length: " << maxPathLength << std::endl; } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 9d4820b51..0b482f57e 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -32,8 +32,10 @@ namespace storm { class SparseMdpLearningModelChecker : public AbstractModelChecker { public: typedef uint32_t StateType; + typedef boost::container::flat_set ChoiceSet; + typedef std::shared_ptr ChoiceSetPointer; - SparseMdpLearningModelChecker(storm::prism::Program const& program); + SparseMdpLearningModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions); virtual bool canHandle(CheckTask const& checkTask) const override; @@ -46,11 +48,15 @@ namespace storm { void updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; - uint32_t sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& upperBounds, StateType const& unexploredMarker); + uint32_t sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& upperBoundsPerState, std::unordered_map const& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker); - StateType sampleSuccessorFromAction(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping); + StateType sampleSuccessorFromAction(StateType chosenAction, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping); - void detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set const& targetStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& unexploredStates, StateType const& unexploredMarker) const; + void detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set& terminalStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker) const; + + std::pair computeValuesOfChoice(uint32_t action, std::vector>> const& transitionMatrix, std::vector const& stateToRowGroupMapping, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker); + + std::pair computeValuesOfState(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& lowerBounds, std::vector const& upperBounds, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker); storm::expressions::Expression getTargetStateExpression(storm::logic::Formula const& subformula); diff --git a/src/storage/MaximalEndComponentDecomposition.cpp b/src/storage/MaximalEndComponentDecomposition.cpp index 87c4bc509..0b9995c08 100644 --- a/src/storage/MaximalEndComponentDecomposition.cpp +++ b/src/storage/MaximalEndComponentDecomposition.cpp @@ -98,7 +98,7 @@ namespace storm { for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { bool choiceContainedInMEC = true; for (auto const& entry : transitionMatrix.getRow(choice)) { - if (entry.getValue() != storm::utility::zero()) { + if (entry.getValue() == storm::utility::zero()) { continue; } @@ -179,6 +179,7 @@ namespace storm { } } + STORM_LOG_ASSERT(!containedChoices.empty(), "The contained choices of any state in an MEC must be non-empty."); newMec.addState(state, std::move(containedChoices)); } From 62db38813b72a7f83ba41898c86fe82b6544592e Mon Sep 17 00:00:00 2001 From: dehnert Date: Mon, 4 Apr 2016 17:54:45 +0200 Subject: [PATCH 14/31] started to refactor learning engine a bit Former-commit-id: e9083011524e5d48737c4aefdd7a2ded669eb457 --- .../SparseMdpLearningModelChecker.cpp | 396 +++++++++--------- .../SparseMdpLearningModelChecker.h | 16 +- 2 files changed, 222 insertions(+), 190 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index aeac6776a..72c7178a0 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -402,6 +402,182 @@ namespace storm { } } } + + template + bool SparseMdpLearningModelChecker::exploreState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType const& currentStateId, storm::generator::CompressedState const& compressedState, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats) { + + bool isTerminalState = false; + bool isTargetState = false; + + // Map the unexplored state to a row group. + stateToRowGroupMapping[currentStateId] = rowGroupIndices.size() - 1; + STORM_LOG_TRACE("Assigning row group " << stateToRowGroupMapping[currentStateId] << " to state " << currentStateId << "."); + lowerBoundsPerState.push_back(storm::utility::zero()); + upperBoundsPerState.push_back(storm::utility::one()); + + // Before generating the behavior of the state, we need to determine whether it's a target state that + // does not need to be expanded. + generator.load(compressedState); + if (generator.satisfies(targetStateExpression)) { + ++stats.numberOfTargetStates; + isTargetState = true; + isTerminalState = true; + } else { + STORM_LOG_TRACE("Exploring state."); + + // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. + storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); + STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); + + // Clumsily check whether we have found a state that forms a trivial BMEC. + if (behavior.getNumberOfChoices() == 0) { + isTerminalState = true; + } else if (behavior.getNumberOfChoices() == 1) { + auto const& onlyChoice = *behavior.begin(); + if (onlyChoice.size() == 1) { + auto const& onlyEntry = *onlyChoice.begin(); + if (onlyEntry.first == currentStateId) { + isTerminalState = true; + } + } + } + + // If the state was neither a trivial (non-accepting) terminal state nor a target state, we + // need to store its behavior. + if (!isTerminalState) { + // Next, we insert the behavior into our matrix structure. + StateType startRow = matrix.size(); + matrix.resize(startRow + behavior.getNumberOfChoices()); + + // Terminate the row group. + rowGroupIndices.push_back(matrix.size()); + + uint32_t currentAction = 0; + for (auto const& choice : behavior) { + for (auto const& entry : choice) { + std::cout << "adding " << currentStateId << " -> " << entry.first << " with prob " << entry.second << std::endl; + matrix[startRow + currentAction].emplace_back(entry.first, entry.second); + } + + lowerBoundsPerAction.push_back(storm::utility::zero()); + upperBoundsPerAction.push_back(storm::utility::one()); + + std::pair values = computeValuesOfChoice(startRow + currentAction, matrix, stateToRowGroupMapping, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + lowerBoundsPerAction.back() = values.first; + upperBoundsPerAction.back() = values.second; + + STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << lowerBoundsPerAction.back() << " and " << upperBoundsPerAction.back() << "."); + + ++currentAction; + } + + std::pair values = computeValuesOfState(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + lowerBoundsPerState.back() = values.first; + upperBoundsPerState.back() = values.second; + + STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << lowerBoundsPerState.back() << " and " << upperBoundsPerState.back() << "."); + } + } + + if (isTerminalState) { + STORM_LOG_TRACE("State does not need to be explored, because it is " << (isTargetState ? "a target state" : "a rejecting terminal state") << "."); + terminalStates.insert(currentStateId); + + if (isTargetState) { + lowerBoundsPerState.back() = storm::utility::one(); + lowerBoundsPerAction.push_back(storm::utility::one()); + upperBoundsPerAction.push_back(storm::utility::one()); + } else { + upperBoundsPerState.back() = storm::utility::zero(); + lowerBoundsPerAction.push_back(storm::utility::zero()); + upperBoundsPerAction.push_back(storm::utility::zero()); + } + + // Increase the size of the matrix, but leave the row empty. + matrix.resize(matrix.size() + 1); + + // Terminate the row group. + rowGroupIndices.push_back(matrix.size()); + } + + return isTerminalState; + } + + template + bool SparseMdpLearningModelChecker::samplePathFromState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType initialStateIndex, std::vector>& stateActionStack, std::unordered_map& unexploredStates, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& stateToLeavingChoicesOfEndComponent, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats) { + + // Start the search from the initial state. + stateActionStack.push_back(std::make_pair(initialStateIndex, 0)); + + bool foundTerminalState = false; + while (!foundTerminalState) { + StateType const& currentStateId = stateActionStack.back().first; + STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); + + // If the state is not yet explored, we need to retrieve its behaviors. + auto unexploredIt = unexploredStates.find(currentStateId); + if (unexploredIt != unexploredStates.end()) { + STORM_LOG_TRACE("State was not yet explored."); + + // Explore the previously unexplored state. + foundTerminalState = exploreState(generator, stateToIdCallback, currentStateId, unexploredIt->second); + unexploredStates.erase(unexploredIt); + } else { + // If the state was already explored, we check whether it is a terminal state or not. + if (terminalStates.find(currentStateId) != terminalStates.end()) { + STORM_LOG_TRACE("Found already explored terminal state: " << currentStateId << "."); + foundTerminalState = true; + } + } + + // If the state was not a terminal state, we continue the path search and sample the next state. + if (!foundTerminalState) { + std::cout << "(2) stack is:" << std::endl; + for (auto const& el : stateActionStack) { + std::cout << el.first << "-[" << el.second << "]-> "; + } + std::cout << std::endl; + + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; + for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { + std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; + } + std::cout << std::endl; + } else { + std::cout << "state " << state << " is unexplored" << std::endl; + } + } + + // At this point, we can be sure that the state was expanded and that we can sample according to the + // probabilities in the matrix. + uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); + stateActionStack.back().second = chosenAction; + STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); + + StateType successor = sampleSuccessorFromAction(chosenAction, matrix, rowGroupIndices, stateToRowGroupMapping); + STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << "."); + + // Put the successor state and a dummy action on top of the stack. + stateActionStack.emplace_back(successor, 0); + stats.maxPathLength = std::max(stats.maxPathLength, stateActionStack.size()); + + // If the current path length exceeds the threshold and the model is a nondeterministic one, we + // perform an EC detection. + if (stateActionStack.size() > stats.pathLengthUntilEndComponentDetection && !program.isDeterministicModel()) { + detectEndComponents(stateActionStack, terminalStates, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); + + // Abort the current search. + STORM_LOG_TRACE("Aborting the search after EC detection."); + stateActionStack.clear(); + break; + } + } + } + + return foundTerminalState; + } template std::tuple::StateType, ValueType, ValueType> SparseMdpLearningModelChecker::performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage& stateStorage, storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& unexploredStates, StateType const& unexploredMarker) { @@ -426,215 +602,57 @@ namespace storm { // Now perform the actual sampling. std::vector> stateActionStack; - - std::size_t iterations = 0; - std::size_t maxPathLength = 0; - std::size_t numberOfTargetStates = 0; - std::size_t pathLengthUntilEndComponentDetection = 27; + + Statistics stats; bool convergenceCriterionMet = false; while (!convergenceCriterionMet) { - // Start the search from the initial state. - stateActionStack.push_back(std::make_pair(initialStateIndex, 0)); + bool result = samplePathFromState(generator, stateToIdCallback, initialStateIndex, stateActionStack, unexploredStates, unexploredMarker, terminalStates, matrix, rowGroupIndices, stateToRowGroupMapping, stateToLeavingChoicesOfEndComponent, targetStateExpression, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, stats); - bool foundTerminalState = false; - bool foundTargetState = false; - while (!foundTerminalState) { - StateType const& currentStateId = stateActionStack.back().first; - STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); - - // If the state is not yet expanded, we need to retrieve its behaviors. - auto unexploredIt = unexploredStates.find(currentStateId); - if (unexploredIt != unexploredStates.end()) { - STORM_LOG_TRACE("State was not yet explored."); - - // Map the unexplored state to a row group. - stateToRowGroupMapping[currentStateId] = rowGroupIndices.size() - 1; - STORM_LOG_TRACE("Assigning row group " << stateToRowGroupMapping[currentStateId] << " to state " << currentStateId << "."); - lowerBoundsPerState.push_back(storm::utility::zero()); - upperBoundsPerState.push_back(storm::utility::one()); - - // We need to get the compressed state back from the id to explore it. - STORM_LOG_ASSERT(unexploredIt != unexploredStates.end(), "Unable to find unexplored state " << currentStateId << "."); - storm::storage::BitVector const& currentState = unexploredIt->second; - - // Before generating the behavior of the state, we need to determine whether it's a target state that - // does not need to be expanded. - generator.load(currentState); - if (generator.satisfies(targetStateExpression)) { - STORM_LOG_TRACE("State does not need to be expanded, because it is a target state. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - ++numberOfTargetStates; - foundTargetState = true; - foundTerminalState = true; - } else { - STORM_LOG_TRACE("Exploring state."); - - // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. - storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); - STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); - - // Clumsily check whether we have found a state that forms a trivial BMEC. - if (behavior.getNumberOfChoices() == 0) { - foundTerminalState = true; - } else if (behavior.getNumberOfChoices() == 1) { - auto const& onlyChoice = *behavior.begin(); - if (onlyChoice.size() == 1) { - auto const& onlyEntry = *onlyChoice.begin(); - if (onlyEntry.first == currentStateId) { - foundTerminalState = true; - } - } - } - - // If the state was neither a trivial (non-accepting) terminal state nor a target state, we - // need to store its behavior. - if (!foundTerminalState) { - // Next, we insert the behavior into our matrix structure. - StateType startRow = matrix.size(); - matrix.resize(startRow + behavior.getNumberOfChoices()); - - // Terminate the row group. - rowGroupIndices.push_back(matrix.size()); - - uint32_t currentAction = 0; - for (auto const& choice : behavior) { - for (auto const& entry : choice) { - std::cout << "adding " << currentStateId << " -> " << entry.first << " with prob " << entry.second << std::endl; - matrix[startRow + currentAction].emplace_back(entry.first, entry.second); - } - - lowerBoundsPerAction.push_back(storm::utility::zero()); - upperBoundsPerAction.push_back(storm::utility::one()); - - std::pair values = computeValuesOfChoice(startRow + currentAction, matrix, stateToRowGroupMapping, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - lowerBoundsPerAction.back() = values.first; - upperBoundsPerAction.back() = values.second; - - STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << lowerBoundsPerAction.back() << " and " << upperBoundsPerAction.back() << "."); - - ++currentAction; - } - - std::pair values = computeValuesOfState(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - lowerBoundsPerState.back() = values.first; - upperBoundsPerState.back() = values.second; - - STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << lowerBoundsPerState.back() << " and " << upperBoundsPerState.back() << "."); - } - } - - if (foundTerminalState) { - STORM_LOG_TRACE("State does not need to be explored, because it is " << (foundTargetState ? "a target state" : "a rejecting terminal state") << "."); - terminalStates.insert(currentStateId); - - if (foundTargetState) { - lowerBoundsPerState.back() = storm::utility::one(); - lowerBoundsPerAction.push_back(storm::utility::one()); - upperBoundsPerAction.push_back(storm::utility::one()); - } else { - upperBoundsPerState.back() = storm::utility::zero(); - lowerBoundsPerAction.push_back(storm::utility::zero()); - upperBoundsPerAction.push_back(storm::utility::zero()); - } - - // Increase the size of the matrix, but leave the row empty. - matrix.resize(matrix.size() + 1); - - // Terminate the row group. - rowGroupIndices.push_back(matrix.size()); - } - - // Now that we have explored the state, we can dispose of it. - unexploredStates.erase(unexploredIt); - } else { - if (terminalStates.find(currentStateId) != terminalStates.end()) { - STORM_LOG_TRACE("Found already explored terminal state: " << currentStateId << "."); - foundTerminalState = true; - } - } - - if (foundTerminalState) { - // Update the bounds along the path to the terminal state. - STORM_LOG_TRACE("Found terminal state, updating probabilities along path."); - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - } else { - std::cout << "(2) stack is:" << std::endl; - for (auto const& el : stateActionStack) { - std::cout << el.first << "-[" << el.second << "]-> "; - } - std::cout << std::endl; - - for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { - if (stateToRowGroupMapping[state] != unexploredMarker) { - std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; - for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { - std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; - } - std::cout << std::endl; - } else { - std::cout << "state " << state << " is unexplored" << std::endl; - } - } - - // At this point, we can be sure that the state was expanded and that we can sample according to the - // probabilities in the matrix. - uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); - stateActionStack.back().second = chosenAction; - STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); - - StateType successor = sampleSuccessorFromAction(chosenAction, matrix, rowGroupIndices, stateToRowGroupMapping); - STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << "."); - - // Put the successor state and a dummy action on top of the stack. - stateActionStack.emplace_back(successor, 0); - maxPathLength = std::max(maxPathLength, stateActionStack.size()); - - // If the current path length exceeds the threshold and the model is a nondeterministic one, we - // perform an EC detection. - if (stateActionStack.size() > pathLengthUntilEndComponentDetection && !program.isDeterministicModel()) { - detectEndComponents(stateActionStack, terminalStates, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); - - // Abort the current search. - STORM_LOG_TRACE("Aborting the search after EC detection."); - stateActionStack.clear(); - break; - } - } + // If a terminal state was found, we update the probabilities along the path contained in the stack. + if (result) { + // Update the bounds along the path to the terminal state. + STORM_LOG_TRACE("Found terminal state, updating probabilities along path."); + updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + } else { + // If not terminal state was found, the search aborted, possibly because of an EC-detection. In this + // case, we cannot update the probabilities. + STORM_LOG_TRACE("Did not find terminal state."); } - + // Sanity check of results. for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { if (stateToRowGroupMapping[state] != unexploredMarker) { STORM_LOG_ASSERT(lowerBoundsPerState[stateToRowGroupMapping[state]] <= upperBoundsPerState[stateToRowGroupMapping[state]], "The bounds for state " << state << " are not in a sane relation: " << lowerBoundsPerState[stateToRowGroupMapping[state]] << " > " << upperBoundsPerState[stateToRowGroupMapping[state]] << "."); } } - - for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { - if (stateToRowGroupMapping[state] != unexploredMarker) { - std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; - for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { - std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; - } - std::cout << std::endl; - } else { - std::cout << "state " << state << " is unexplored" << std::endl; - } - } - + +// TODO: remove debug output when superfluous +// for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { +// if (stateToRowGroupMapping[state] != unexploredMarker) { +// std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; +// for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { +// std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; +// } +// std::cout << std::endl; +// } else { +// std::cout << "state " << state << " is unexplored" << std::endl; +// } +// } STORM_LOG_DEBUG("Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)."); STORM_LOG_DEBUG("Value of initial state is in [" << lowerBoundsPerState[initialStateIndex] << ", " << upperBoundsPerState[initialStateIndex] << "]."); ValueType difference = upperBoundsPerState[initialStateIndex] - lowerBoundsPerState[initialStateIndex]; - STORM_LOG_DEBUG("Difference after iteration " << iterations << " is " << difference << "."); + STORM_LOG_DEBUG("Difference after iteration " << stats.iterations << " is " << difference << "."); convergenceCriterionMet = difference < 1e-6; - ++iterations; + ++stats.iterations; } if (storm::settings::generalSettings().isShowStatisticsSet()) { std::cout << std::endl << "Learning summary -------------------------" << std::endl; - std::cout << "Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored, " << numberOfTargetStates << " target states)" << std::endl; - std::cout << "Sampling iterations: " << iterations << std::endl; - std::cout << "Maximal path length: " << maxPathLength << std::endl; + std::cout << "Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored, " << stats.numberOfTargetStates << " target states)" << std::endl; + std::cout << "Sampling iterations: " << stats.iterations << std::endl; + std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; } return std::make_tuple(initialStateIndex, lowerBoundsPerState[initialStateIndex], upperBoundsPerState[initialStateIndex]); diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 0b482f57e..5bc8adb40 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -27,7 +27,7 @@ namespace storm { } namespace modelchecker { - + template class SparseMdpLearningModelChecker : public AbstractModelChecker { public: @@ -42,6 +42,20 @@ namespace storm { virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; private: + struct Statistics { + Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), pathLengthUntilEndComponentDetection(27) { + // Intentionally left empty. + } + std::size_t iterations; + std::size_t maxPathLength; + std::size_t numberOfTargetStates; + std::size_t pathLengthUntilEndComponentDetection; + }; + + bool exploreState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType const& currentStateId, storm::generator::CompressedState const& compressedState, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats); + + bool samplePathFromState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType initialStateIndex, std::vector>& stateActionStack, std::unordered_map& unexploredStates, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& stateToLeavingChoicesOfEndComponent, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats); + std::tuple performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage& stateStorage, storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& unexploredStates, StateType const& unexploredMarker); void updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; From 509243532932ae81b6c2851de4826a1c1afd0b37 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 5 Apr 2016 18:25:47 +0200 Subject: [PATCH 15/31] started refactoring learning model checker Former-commit-id: b9e6015ae40a7a5425f1a5d6c725bf14471898ee --- .../SparseMdpLearningModelChecker.cpp | 852 ++++++++---------- .../SparseMdpLearningModelChecker.h | 274 +++++- 2 files changed, 621 insertions(+), 505 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 72c7178a0..67d3c6476 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -1,11 +1,8 @@ #include "src/modelchecker/reachability/SparseMdpLearningModelChecker.h" #include "src/storage/SparseMatrix.h" -#include "src/storage/sparse/StateStorage.h" #include "src/storage/MaximalEndComponentDecomposition.h" -#include "src/generator/PrismNextStateGenerator.h" - #include "src/logic/FragmentSpecification.h" #include "src/utility/prism.h" @@ -18,6 +15,7 @@ #include "src/settings/modules/GeneralSettings.h" #include "src/utility/macros.h" +#include "src/exceptions/InvalidPropertyException.h" #include "src/exceptions/NotSupportedException.h" namespace storm { @@ -36,239 +34,297 @@ namespace storm { } template - void SparseMdpLearningModelChecker::updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const { - // Find out which row of the matrix we have to consider for the given action. - StateType sourceRow = action; - - // Compute the new lower/upper values of the action. - ValueType newLowerValue = storm::utility::zero(); - ValueType newUpperValue = storm::utility::zero(); - -// boost::optional loopProbability; - for (auto const& element : transitionMatrix[sourceRow]) { - // If the element is a self-loop, we treat the probability by a proper rescaling after the loop. -// if (element.getColumn() == sourceStateId) { -// STORM_LOG_TRACE("Found self-loop with probability " << element.getValue() << "."); -// loopProbability = element.getValue(); -// continue; -// } - - std::cout << "lower += " << element.getValue() << " * state[" << element.getColumn() << "] = " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::zero() : lowerBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << std::endl; - if (stateToRowGroupMapping[element.getColumn()] != unexploredMarker) { - std::cout << "upper bounds per state @ " << stateToRowGroupMapping[element.getColumn()] << " is " << upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]] << std::endl; - } - std::cout << "upper += " << element.getValue() << " * state[" << element.getColumn() << "] = " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << std::endl; - newLowerValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::zero() : lowerBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); - newUpperValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); - std::cout << "after iter " << newLowerValue << " and " << newUpperValue << std::endl; - } + std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { + storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); + storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); + STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "For nondeterministic systems, an optimization direction (min/max) must be given in the property."); + STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); - // If there was a self-loop with probability p, we scale the probabilities by 1/(1-p). -// if (loopProbability) { -// STORM_LOG_TRACE("Scaling values " << newLowerValue << " and " << newUpperValue << " with " << (storm::utility::one()/(storm::utility::one() - loopProbability.get())) << "."); -// newLowerValue = newLowerValue / (storm::utility::one() - loopProbability.get()); -// newUpperValue = newUpperValue / (storm::utility::one() - loopProbability.get()); -// } + StateGeneration stateGeneration(storm::generator::PrismNextStateGenerator(program, variableInformation, false), getTargetStateExpression(subformula)); - // And set them as the current value. - std::cout << newLowerValue << " vs " << newUpperValue << std::endl; - STORM_LOG_ASSERT(newLowerValue <= newUpperValue, "Lower bound must always be smaller or equal than upper bound."); - STORM_LOG_TRACE("Updating lower value of action " << action << " of state " << sourceStateId << " to " << newLowerValue << " (was " << lowerBoundsPerAction[action] << ")."); - lowerBoundsPerAction[action] = newLowerValue; - STORM_LOG_TRACE("Updating upper value of action " << action << " of state " << sourceStateId << " to " << newUpperValue << " (was " << upperBoundsPerAction[action] << ")."); - upperBoundsPerAction[action] = newUpperValue; + ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true)); + explorationInformation.optimizationDirection = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; - // Check if we need to update the values for the states. - if (lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] < newLowerValue) { - STORM_LOG_TRACE("Got new lower bound for state " << sourceStateId << ": " << newLowerValue << " (was " << lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] << ")."); - lowerBoundsPerState[stateToRowGroupMapping[sourceStateId]] = newLowerValue; - } - - uint32_t sourceRowGroup = stateToRowGroupMapping[sourceStateId]; - if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { - if (rowGroupIndices[sourceRowGroup + 1] - rowGroupIndices[sourceRowGroup] > 1) { - ValueType max = storm::utility::zero(); - - for (uint32_t currentAction = rowGroupIndices[sourceRowGroup]; currentAction < rowGroupIndices[sourceRowGroup + 1]; ++currentAction) { - std::cout << "cur: " << currentAction << std::endl; - if (currentAction == action) { - continue; - } - - ValueType currentValue = storm::utility::zero(); - for (auto const& element : transitionMatrix[currentAction]) { - currentValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); - } - max = std::max(max, currentValue); - std::cout << "max is " << max << std::endl; - } - - newUpperValue = std::max(newUpperValue, max); - } - - if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { - STORM_LOG_TRACE("Got new upper bound for state " << sourceStateId << ": " << newUpperValue << " (was " << upperBoundsPerState[sourceRowGroup] << ")."); - std::cout << "writing at index " << sourceRowGroup << std::endl; - upperBoundsPerState[sourceRowGroup] = newUpperValue; - } - } - } - - template - void SparseMdpLearningModelChecker::updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const { + // The first row group starts at action 0. + explorationInformation.newRowGroup(0); - std::cout << "stack is:" << std::endl; - for (auto const& el : stateActionStack) { - std::cout << el.first << "-[" << el.second << "]-> "; - } - std::cout << std::endl; + // Create a callback for the next-state generator to enable it to request the index of states. + std::function stateToIdCallback = createStateToIdCallback(explorationInformation); - stateActionStack.pop_back(); - while (!stateActionStack.empty()) { - updateProbabilities(stateActionStack.back().first, stateActionStack.back().second, transitionMatrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - stateActionStack.pop_back(); - } + // Compute and return result. + std::tuple boundsForInitialState = performLearningProcedure(stateGeneration, explorationInformation); + return std::make_unique>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); } template - std::pair SparseMdpLearningModelChecker::computeValuesOfChoice(uint32_t action, std::vector>> const& transitionMatrix, std::vector const& stateToRowGroupMapping, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker) { - std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); - for (auto const& element : transitionMatrix[action]) { - result.first += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::zero() : lowerBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); - result.second += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + storm::expressions::Expression SparseMdpLearningModelChecker::getTargetStateExpression(storm::logic::Formula const& subformula) const { + storm::expressions::Expression result; + if (subformula.isAtomicExpressionFormula()) { + result = subformula.asAtomicExpressionFormula().getExpression(); + } else { + result = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); } return result; } template - std::pair SparseMdpLearningModelChecker::computeValuesOfState(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& lowerBounds, std::vector const& upperBounds, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker) { - StateType sourceRowGroup = stateToRowGroupMapping[currentStateId]; - std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); - for (uint32_t choice = rowGroupIndices[sourceRowGroup]; choice < rowGroupIndices[sourceRowGroup + 1]; ++choice) { - std::pair choiceValues = computeValuesOfChoice(choice, transitionMatrix, stateToRowGroupMapping, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - result.first = std::max(choiceValues.first, result.first); - result.second = std::max(choiceValues.second, result.second); - } - return result; + std::function::StateType (storm::generator::CompressedState const&)> SparseMdpLearningModelChecker::createStateToIdCallback(ExplorationInformation& explorationInformation) const { + return [&explorationInformation] (storm::generator::CompressedState const& state) -> StateType { + StateType newIndex = explorationInformation.stateStorage.numberOfStates; + + // Check, if the state was already registered. + std::pair actualIndexBucketPair = explorationInformation.stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); + + if (actualIndexBucketPair.first == newIndex) { + explorationInformation.addUnexploredState(state); + } + + return actualIndexBucketPair.first; + }; } template - uint32_t SparseMdpLearningModelChecker::sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& upperBoundsPerState, std::unordered_map const& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker) { + std::tuple::StateType, ValueType, ValueType> SparseMdpLearningModelChecker::performLearningProcedure(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { - StateType rowGroup = stateToRowGroupMapping[currentStateId]; + // Generate the initial state so we know where to start the simulation. + explorationInformation.setInitialStates(stateGeneration.getInitialStates()); + STORM_LOG_THROW(explorationInformation.getNumberOfInitialStates() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); + StateType initialStateIndex = explorationInformation.getFirstInitialState(); - // First, determine all maximizing actions. - std::vector allMaxActions; + // Create a structure that holds the bounds for the states and actions. + BoundValues bounds; + + // Create a stack that is used to track the path we sampled. + StateActionStack stack; - for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { - if (stateToRowGroupMapping[state] != unexploredMarker) { - std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [x, " << upperBoundsPerState[stateToRowGroupMapping[state]] << "]" << std::endl; + // Now perform the actual sampling. + Statistics stats; + bool convergenceCriterionMet = false; + while (!convergenceCriterionMet) { + bool result = samplePathFromState(stateGeneration, explorationInformation, stack, bounds, stats); + + // If a terminal state was found, we update the probabilities along the path contained in the stack. + if (result) { + // Update the bounds along the path to the terminal state. + STORM_LOG_TRACE("Found terminal state, updating probabilities along path."); + updateProbabilityBoundsAlongSampledPath(stack, explorationInformation, bounds); } else { - std::cout << "state " << state << " is unexplored" << std::endl; + // If not terminal state was found, the search aborted, possibly because of an EC-detection. In this + // case, we cannot update the probabilities. + STORM_LOG_TRACE("Did not find terminal state."); } + + STORM_LOG_DEBUG("Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << "explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored)."); + STORM_LOG_DEBUG("Value of initial state is in [" << bounds.getLowerBoundForState(initialStateIndex, explorationInformation) << ", " << bounds.getUpperBoundForState(initialStateIndex, explorationInformation) << "]."); + ValueType difference = bounds.getDifferenceOfStateBounds(initialStateIndex, explorationInformation); + STORM_LOG_DEBUG("Difference after iteration " << stats.iterations << " is " << difference << "."); + convergenceCriterionMet = difference < 1e-6; + + ++stats.iterations; } - // Determine the maximal value of any action. - ValueType max = 0; - auto choicesInEcIt = stateToLeavingChoicesOfEndComponent.find(currentStateId); - if (choicesInEcIt != stateToLeavingChoicesOfEndComponent.end()) { - STORM_LOG_TRACE("Sampling from actions leaving the previously detected EC."); - for (auto const& row : *choicesInEcIt->second) { - ValueType current = 0; - for (auto const& element : transitionMatrix[row]) { - current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + if (storm::settings::generalSettings().isShowStatisticsSet()) { + std::cout << std::endl << "Learning summary -------------------------" << std::endl; + std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << "explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target states)" << std::endl; + std::cout << "Sampling iterations: " << stats.iterations << std::endl; + std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; + } + + return std::make_tuple(initialStateIndex, bounds.getLowerBoundForState(initialStateIndex, explorationInformation), bounds.getUpperBoundForState(initialStateIndex, explorationInformation)); + } + + template + bool SparseMdpLearningModelChecker::samplePathFromState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const { + + // Start the search from the initial state. + stack.push_back(std::make_pair(explorationInformation.getFirstInitialState(), 0)); + + bool foundTerminalState = false; + while (!foundTerminalState) { + StateType const& currentStateId = stack.back().first; + STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); + + // If the state is not yet explored, we need to retrieve its behaviors. + auto unexploredIt = explorationInformation.unexploredStates.find(currentStateId); + if (unexploredIt != explorationInformation.unexploredStates.end()) { + STORM_LOG_TRACE("State was not yet explored."); + + // Explore the previously unexplored state. + storm::generator::CompressedState const& compressedState = unexploredIt->second; + foundTerminalState = exploreState(stateGeneration, currentStateId, compressedState, explorationInformation, bounds, stats); + explorationInformation.unexploredStates.erase(unexploredIt); + } else { + // If the state was already explored, we check whether it is a terminal state or not. + if (explorationInformation.isTerminal(currentStateId)) { + STORM_LOG_TRACE("Found already explored terminal state: " << currentStateId << "."); + foundTerminalState = true; } + } + + // If the state was not a terminal state, we continue the path search and sample the next state. + if (!foundTerminalState) { + // At this point, we can be sure that the state was expanded and that we can sample according to the + // probabilities in the matrix. + uint32_t chosenAction = sampleFromMaxActions(currentStateId, explorationInformation, bounds); + stack.back().second = chosenAction; + STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); + + StateType successor = sampleSuccessorFromAction(chosenAction, explorationInformation); + STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << "."); + + // Put the successor state and a dummy action on top of the stack. + stack.emplace_back(successor, 0); + stats.maxPathLength = std::max(stats.maxPathLength, stack.size()); - max = std::max(max, current); + // If the current path length exceeds the threshold and the model is a nondeterministic one, we + // perform an EC detection. + if (stack.size() > stats.pathLengthUntilEndComponentDetection && !program.isDeterministicModel()) { + detectEndComponents(stack, explorationInformation, bounds); + + // Abort the current search. + STORM_LOG_TRACE("Aborting the search after EC detection."); + stack.clear(); + break; + } } + } + + return foundTerminalState; + } + + template + bool SparseMdpLearningModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { + + bool isTerminalState = false; + bool isTargetState = false; + + // Before generating the behavior of the state, we need to determine whether it's a target state that + // does not need to be expanded. + stateGeneration.generator.load(currentState); + if (stateGeneration.isTargetState()) { + ++stats.numberOfTargetStates; + isTargetState = true; + isTerminalState = true; } else { - STORM_LOG_TRACE("Sampling from actions leaving the state."); - for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { - ValueType current = 0; - for (auto const& element : transitionMatrix[row]) { - current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + STORM_LOG_TRACE("Exploring state."); + + // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. + storm::generator::StateBehavior behavior = stateGeneration.expand(); + STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); + + // Clumsily check whether we have found a state that forms a trivial BMEC. + if (behavior.getNumberOfChoices() == 0) { + isTerminalState = true; + } else if (behavior.getNumberOfChoices() == 1) { + auto const& onlyChoice = *behavior.begin(); + if (onlyChoice.size() == 1) { + auto const& onlyEntry = *onlyChoice.begin(); + if (onlyEntry.first == currentStateId) { + isTerminalState = true; + } } - max = std::max(max, current); } - } - - STORM_LOG_TRACE("Looking for action with value " << max << "."); + + // If the state was neither a trivial (non-accepting) terminal state nor a target state, we + // need to store its behavior. + if (!isTerminalState) { + // Next, we insert the behavior into our matrix structure. + StateType startRow = explorationInformation.matrix.size(); + explorationInformation.addRowsToMatrix(behavior.getNumberOfChoices()); + + // Terminate the row group. + explorationInformation.rowGroupIndices.push_back(explorationInformation.matrix.size()); + + ActionType currentAction = 0; + for (auto const& choice : behavior) { + for (auto const& entry : choice) { + std::cout << "adding " << currentStateId << " -> " << entry.first << " with prob " << entry.second << std::endl; + explorationInformation.matrix[startRow + currentAction].emplace_back(entry.first, entry.second); + } + + bounds.initializeActionBoundsForNextAction(computeBoundsOfAction(startRow + currentAction, explorationInformation, bounds)); - for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { - if (stateToRowGroupMapping[state] != unexploredMarker) { - std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [x, " << upperBoundsPerState[stateToRowGroupMapping[state]] << "]" << std::endl; + STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << bounds.getLowerBoundForAction(startRow + currentAction) << " and " << bounds.getUpperBoundForAction(startRow + currentAction) << "."); + + ++currentAction; + } + + bounds.initializeStateBoundsForNextState(computeBoundsOfState(currentStateId, explorationInformation, bounds)); + STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << bounds.getLowerBoundForState(currentStateId) << " and " << bounds.getUpperBoundForState(currentStateId) << "."); + } + } + + if (isTerminalState) { + STORM_LOG_TRACE("State does not need to be explored, because it is " << (isTargetState ? "a target state" : "a rejecting terminal state") << "."); + explorationInformation.addTerminalState(currentStateId); + + if (isTargetState) { + bounds.initializeStateBoundsForNextState(std::make_pair(storm::utility::one(), storm::utility::one())); + bounds.initializeStateBoundsForNextAction(std::make_pair(storm::utility::one(), storm::utility::one())); } else { - std::cout << "state " << state << " is unexplored" << std::endl; + bounds.initializeStateBoundsForNextState(std::make_pair(storm::utility::zero(), storm::utility::zero())); + bounds.initializeStateBoundsForNextAction(std::make_pair(storm::utility::zero(), storm::utility::zero())); } + + // Increase the size of the matrix, but leave the row empty. + explorationInformation.addRowsToMatrix(1); + + // Terminate the row group. + explorationInformation.newRowGroup(); } - if (choicesInEcIt != stateToLeavingChoicesOfEndComponent.end()) { + // Finally, map the unexplored state to the row group. + explorationInformation.assignStateToNextRowGroup(currentStateId); + STORM_LOG_TRACE("Assigning row group " << explorationInformation.getRowGroup(currentStateId) << " to state " << currentStateId << "."); + + return isTerminalState; + } + + template + uint32_t SparseMdpLearningModelChecker::sampleMaxAction(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + StateType rowGroup = explorationInformation.getRowGroup(currentStateId); + + // First, determine all maximizing actions. + std::vector allMaxActions; + + // Determine the values of all available actions. + std::vector> actionValues; + auto choicesInEcIt = explorationInformation.stateToLeavingChoicesOfEndComponent.find(currentStateId); + if (choicesInEcIt != explorationInformation.stateToLeavingChoicesOfEndComponent.end()) { + STORM_LOG_TRACE("Sampling from actions leaving the previously detected EC."); for (auto const& row : *choicesInEcIt->second) { - ValueType current = 0; - for (auto const& element : transitionMatrix[row]) { - current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); - } - STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); - - // If the action is one of the maximizing ones, insert it into our list. - if (comparator.isEqual(current, max)) { - allMaxActions.push_back(row); - } + actionValues.push_back(std::make_pair(row, computeUpperBoundOfAction(row, explorationInformation, bounds))); } } else { - for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { - ValueType current = 0; - for (auto const& element : transitionMatrix[row]) { - if (stateToRowGroupMapping[element.getColumn()] != unexploredMarker) { - std::cout << "upper bounds per state @ " << stateToRowGroupMapping[element.getColumn()] << " is " << upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]] << std::endl; - } - std::cout << "+= " << element.getValue() << " * " << "state[" << element.getColumn() << "] = " << (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]) << std::endl; - current += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); - } - STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); - - // If the action is one of the maximizing ones, insert it into our list. - if (comparator.isEqual(current, max)) { - allMaxActions.push_back(row); - } + STORM_LOG_TRACE("Sampling from actions leaving the state."); + + for (uint32_t row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) { + actionValues.push_back(std::make_pair(row, computeUpperBoundOfAction(row, explorationInformation, bounds))); } } - STORM_LOG_ASSERT(!allMaxActions.empty(), "Must have at least one maximizing action."); + std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return b.second > a.second; } ); + auto end = std::equal_range(actionValues.begin(), actionValues.end(), [this] (std::pair const& a, std::pair const& b) { return comparator.isEqual(a.second, b.second); } ); // Now sample from all maximizing actions. - std::uniform_int_distribution distribution(0, allMaxActions.size() - 1); - return allMaxActions[distribution(randomGenerator)]; + std::uniform_int_distribution distribution(0, std::distance(actionValues.begin(), end)); + return actionValues[distribution(randomGenerator)].first; } template - typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(StateType chosenAction, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping) { - uint32_t row = chosenAction; - + typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation) const { // TODO: precompute this? - std::vector probabilities(transitionMatrix[row].size()); - std::transform(transitionMatrix[row].begin(), transitionMatrix[row].end(), probabilities.begin(), [] (storm::storage::MatrixEntry const& entry) { return entry.getValue(); } ); + std::vector probabilities(explorationInformation.getRowOfMatrix(chosenAction).size()); + std::transform(explorationInformation.getRowOfMatrix(chosenAction).begin(), explorationInformation.getRowOfMatrix(chosenAction).end(), probabilities.begin(), [] (storm::storage::MatrixEntry const& entry) { return entry.getValue(); } ); // Now sample according to the probabilities. std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); StateType offset = distribution(randomGenerator); - return transitionMatrix[row][offset].getColumn(); + return explorationInformation.getRowOfMatrix(chosenAction)[offset].getColumn(); } template - storm::expressions::Expression SparseMdpLearningModelChecker::getTargetStateExpression(storm::logic::Formula const& subformula) { - storm::expressions::Expression result; - if (subformula.isAtomicExpressionFormula()) { - result = subformula.asAtomicExpressionFormula().getExpression(); - } else { - result = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); - } - return result; - } - - template - void SparseMdpLearningModelChecker::detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set& terminalStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker) const { - + void SparseMdpLearningModelChecker::detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds) const { STORM_LOG_TRACE("Starting EC detection."); // Outline: @@ -281,13 +337,14 @@ namespace storm { // Determine the set of states that was expanded. std::vector relevantStates; - for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { - if (stateToRowGroupMapping[state] != unexploredMarker) { + for (StateType state = 0; state < explorationInformation.stateStorage.numberOfStates; ++state) { + if (!explorationInformation.isUnexplored(state)) { relevantStates.push_back(state); } } + // Sort according to the actual row groups so we can insert the elements in order later. - std::sort(relevantStates.begin(), relevantStates.end(), [&stateToRowGroupMapping] (StateType const& a, StateType const& b) { return stateToRowGroupMapping[a] < stateToRowGroupMapping[b]; }); + std::sort(relevantStates.begin(), relevantStates.end(), [&explorationInformation] (StateType const& a, StateType const& b) { return explorationInformation.getRowGroup(a) < explorationInformation.getRowGroup(b); }); StateType unexploredState = relevantStates.size(); // Create a mapping for faster look-up during the translation of flexible matrix to the real sparse matrix. @@ -301,10 +358,10 @@ namespace storm { StateType currentRow = 0; for (auto const& state : relevantStates) { builder.newRowGroup(currentRow); - StateType rowGroup = stateToRowGroupMapping[state]; - for (auto row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { + StateType rowGroup = explorationInformation.getRowGroup(state); + for (auto row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) { ValueType unexpandedProbability = storm::utility::zero(); - for (auto const& entry : transitionMatrix[row]) { + for (auto const& entry : explorationInformation.getRowOfMatrix(row)) { auto it = relevantStateToNewRowGroupMapping.find(entry.getColumn()); if (it != relevantStateToNewRowGroupMapping.end()) { // If the entry is a relevant state, we copy it over (and compensate for the offset change). @@ -340,41 +397,41 @@ namespace storm { bool containsTargetState = false; // Now we record all choices leaving the EC. - ChoiceSetPointer leavingChoices = std::make_shared(); + ActionSetPointer leavingChoices = std::make_shared(); for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. std::cout << "local state: " << stateAndChoices.first << std::endl; StateType originalState = relevantStates[stateAndChoices.first]; std::cout << "original state: " << originalState << std::endl; - uint32_t originalRowGroup = stateToRowGroupMapping[originalState]; + uint32_t originalRowGroup = explorationInformation.getRowGroup(originalState); std::cout << "original row group: " << originalRowGroup << std::endl; - // TODO: This check for a target state is a bit hackish and only works for max probabilities. - if (!containsTargetState && lowerBoundsPerState[originalRowGroup] == storm::utility::one()) { + // TODO: This checks for a target state is a bit hackish and only works for max probabilities. + if (!containsTargetState && comparator.isOne(bounds.getLowerBoundForRowGroup(originalRowGroup, explorationInformation))) { containsTargetState = true; } auto includedChoicesIt = stateAndChoices.second.begin(); auto includedChoicesIte = stateAndChoices.second.end(); - for (auto choice = rowGroupIndices[originalRowGroup]; choice < rowGroupIndices[originalRowGroup + 1]; ++choice) { + for (auto action = explorationInformation.getStartRowOfGroup(originalRowGroup); action < explorationInformation.getStartRowOfGroup(originalRowGroup + 1); ++action) { if (includedChoicesIt != includedChoicesIte) { STORM_LOG_TRACE("Next (local) choice contained in MEC is " << (*includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first])); - STORM_LOG_TRACE("Current (local) choice iterated is " << (choice - rowGroupIndices[originalRowGroup])); - if (choice - rowGroupIndices[originalRowGroup] != *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { + STORM_LOG_TRACE("Current (local) choice iterated is " << (action - explorationInformation.getStartRowOfGroup(originalRowGroup))); + if (action - explorationInformation.getStartRowOfGroup(originalRowGroup) != *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { STORM_LOG_TRACE("Choice leaves the EC."); - leavingChoices->insert(choice); + leavingChoices->insert(action); } else { STORM_LOG_TRACE("Choice stays in the EC."); ++includedChoicesIt; } } else { STORM_LOG_TRACE("Choice leaves the EC, because there is no more choice staying in the EC."); - leavingChoices->insert(choice); + leavingChoices->insert(action); } } - stateToLeavingChoicesOfEndComponent[originalState] = leavingChoices; + explorationInformation.stateToLeavingChoicesOfEndComponent[originalState] = leavingChoices; } // If one of the states of the EC is a target state, all states in the EC have probability 1. @@ -383,10 +440,10 @@ namespace storm { for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. StateType originalState = relevantStates[stateAndChoices.first]; - - STORM_LOG_TRACE("Setting lower bound of state in row group " << stateToRowGroupMapping[originalState] << " to 1."); - lowerBoundsPerState[stateToRowGroupMapping[originalState]] = storm::utility::one(); - terminalStates.insert(originalState); + + STORM_LOG_TRACE("Setting lower bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 1."); + bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one()); + explorationInformation.addTerminalState(originalState); } } else if (leavingChoices->empty()) { STORM_LOG_TRACE("MEC's leaving choices are empty."); @@ -395,317 +452,138 @@ namespace storm { // Compute the state of the original model that corresponds to the current state. StateType originalState = relevantStates[stateAndChoices.first]; - STORM_LOG_TRACE("Setting upper bound of state in row group " << stateToRowGroupMapping[originalState] << " to 0."); - upperBoundsPerState[stateToRowGroupMapping[originalState]] = storm::utility::zero(); - terminalStates.insert(originalState); + STORM_LOG_TRACE("Setting upper bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 0."); + bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); + explorationInformation.addTerminalState(originalState); } } } } - + template - bool SparseMdpLearningModelChecker::exploreState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType const& currentStateId, storm::generator::CompressedState const& compressedState, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats) { - - bool isTerminalState = false; - bool isTargetState = false; - - // Map the unexplored state to a row group. - stateToRowGroupMapping[currentStateId] = rowGroupIndices.size() - 1; - STORM_LOG_TRACE("Assigning row group " << stateToRowGroupMapping[currentStateId] << " to state " << currentStateId << "."); - lowerBoundsPerState.push_back(storm::utility::zero()); - upperBoundsPerState.push_back(storm::utility::one()); - - // Before generating the behavior of the state, we need to determine whether it's a target state that - // does not need to be expanded. - generator.load(compressedState); - if (generator.satisfies(targetStateExpression)) { - ++stats.numberOfTargetStates; - isTargetState = true; - isTerminalState = true; - } else { - STORM_LOG_TRACE("Exploring state."); - - // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. - storm::generator::StateBehavior behavior = generator.expand(stateToIdCallback); - STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); - - // Clumsily check whether we have found a state that forms a trivial BMEC. - if (behavior.getNumberOfChoices() == 0) { - isTerminalState = true; - } else if (behavior.getNumberOfChoices() == 1) { - auto const& onlyChoice = *behavior.begin(); - if (onlyChoice.size() == 1) { - auto const& onlyEntry = *onlyChoice.begin(); - if (onlyEntry.first == currentStateId) { - isTerminalState = true; - } - } - } - - // If the state was neither a trivial (non-accepting) terminal state nor a target state, we - // need to store its behavior. - if (!isTerminalState) { - // Next, we insert the behavior into our matrix structure. - StateType startRow = matrix.size(); - matrix.resize(startRow + behavior.getNumberOfChoices()); - - // Terminate the row group. - rowGroupIndices.push_back(matrix.size()); - - uint32_t currentAction = 0; - for (auto const& choice : behavior) { - for (auto const& entry : choice) { - std::cout << "adding " << currentStateId << " -> " << entry.first << " with prob " << entry.second << std::endl; - matrix[startRow + currentAction].emplace_back(entry.first, entry.second); - } - - lowerBoundsPerAction.push_back(storm::utility::zero()); - upperBoundsPerAction.push_back(storm::utility::one()); - - std::pair values = computeValuesOfChoice(startRow + currentAction, matrix, stateToRowGroupMapping, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - lowerBoundsPerAction.back() = values.first; - upperBoundsPerAction.back() = values.second; - - STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << lowerBoundsPerAction.back() << " and " << upperBoundsPerAction.back() << "."); - - ++currentAction; - } - - std::pair values = computeValuesOfState(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - lowerBoundsPerState.back() = values.first; - upperBoundsPerState.back() = values.second; - - STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << lowerBoundsPerState.back() << " and " << upperBoundsPerState.back() << "."); - } + ValueType SparseMdpLearningModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType result = storm::utility::zero(); + for (auto const& element : explorationInformation.getRowOfMatrix(action)) { + result += element.getValue() * bounds.getLowerBoundForState(element.getColumn(), explorationInformation); } - - if (isTerminalState) { - STORM_LOG_TRACE("State does not need to be explored, because it is " << (isTargetState ? "a target state" : "a rejecting terminal state") << "."); - terminalStates.insert(currentStateId); - - if (isTargetState) { - lowerBoundsPerState.back() = storm::utility::one(); - lowerBoundsPerAction.push_back(storm::utility::one()); - upperBoundsPerAction.push_back(storm::utility::one()); - } else { - upperBoundsPerState.back() = storm::utility::zero(); - lowerBoundsPerAction.push_back(storm::utility::zero()); - upperBoundsPerAction.push_back(storm::utility::zero()); - } - - // Increase the size of the matrix, but leave the row empty. - matrix.resize(matrix.size() + 1); - - // Terminate the row group. - rowGroupIndices.push_back(matrix.size()); + return result; + } + + template + ValueType SparseMdpLearningModelChecker::computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType result = storm::utility::zero(); + for (auto const& element : explorationInformation.getRowOfMatrix(action)) { + result += element.getValue() * bounds.getUpperBoundForState(element.getColumn(), explorationInformation); } - - return isTerminalState; + return result; } template - bool SparseMdpLearningModelChecker::samplePathFromState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType initialStateIndex, std::vector>& stateActionStack, std::unordered_map& unexploredStates, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& stateToLeavingChoicesOfEndComponent, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats) { - - // Start the search from the initial state. - stateActionStack.push_back(std::make_pair(initialStateIndex, 0)); - - bool foundTerminalState = false; - while (!foundTerminalState) { - StateType const& currentStateId = stateActionStack.back().first; - STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); - - // If the state is not yet explored, we need to retrieve its behaviors. - auto unexploredIt = unexploredStates.find(currentStateId); - if (unexploredIt != unexploredStates.end()) { - STORM_LOG_TRACE("State was not yet explored."); - - // Explore the previously unexplored state. - foundTerminalState = exploreState(generator, stateToIdCallback, currentStateId, unexploredIt->second); - unexploredStates.erase(unexploredIt); - } else { - // If the state was already explored, we check whether it is a terminal state or not. - if (terminalStates.find(currentStateId) != terminalStates.end()) { - STORM_LOG_TRACE("Found already explored terminal state: " << currentStateId << "."); - foundTerminalState = true; - } - } - - // If the state was not a terminal state, we continue the path search and sample the next state. - if (!foundTerminalState) { - std::cout << "(2) stack is:" << std::endl; - for (auto const& el : stateActionStack) { - std::cout << el.first << "-[" << el.second << "]-> "; - } - std::cout << std::endl; - - for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { - if (stateToRowGroupMapping[state] != unexploredMarker) { - std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; - for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { - std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; - } - std::cout << std::endl; - } else { - std::cout << "state " << state << " is unexplored" << std::endl; - } - } - - // At this point, we can be sure that the state was expanded and that we can sample according to the - // probabilities in the matrix. - uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); - stateActionStack.back().second = chosenAction; - STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); - - StateType successor = sampleSuccessorFromAction(chosenAction, matrix, rowGroupIndices, stateToRowGroupMapping); - STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << "."); - - // Put the successor state and a dummy action on top of the stack. - stateActionStack.emplace_back(successor, 0); - stats.maxPathLength = std::max(stats.maxPathLength, stateActionStack.size()); - - // If the current path length exceeds the threshold and the model is a nondeterministic one, we - // perform an EC detection. - if (stateActionStack.size() > stats.pathLengthUntilEndComponentDetection && !program.isDeterministicModel()) { - detectEndComponents(stateActionStack, terminalStates, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, stateToLeavingChoicesOfEndComponent, unexploredMarker); - - // Abort the current search. - STORM_LOG_TRACE("Aborting the search after EC detection."); - stateActionStack.clear(); - break; - } - } + ValueType SparseMdpLearningModelChecker::computeLowerBoundOfState(StateType const& state, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + StateType group = explorationInformation.getRowGroup(state); + ValueType result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { + ValueType actionValue = computeLowerBoundOfAction(action, explorationInformation, bounds); + result = std::max(actionValue, result); } - - return foundTerminalState; + return result; + } + + template + ValueType SparseMdpLearningModelChecker::computeUpperBoundOfState(StateType const& state, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + StateType group = explorationInformation.getRowGroup(state); + ValueType result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { + ValueType actionValue = computeUpperBoundOfAction(action, explorationInformation, bounds); + result = std::max(actionValue, result); + } + return result; } template - std::tuple::StateType, ValueType, ValueType> SparseMdpLearningModelChecker::performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage& stateStorage, storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& unexploredStates, StateType const& unexploredMarker) { - - // Generate the initial state so we know where to start the simulation. - stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback); - STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); - StateType initialStateIndex = stateStorage.initialStateIndices.front(); - - // A set storing all states in which to terminate the search. - boost::container::flat_set terminalStates; - - // Vectors to store the lower/upper bounds for each action (in each state). - std::vector lowerBoundsPerAction; - std::vector upperBoundsPerAction; - std::vector lowerBoundsPerState; - std::vector upperBoundsPerState; - - // Since we might run into end-components, we track a mapping from states in ECs to all leaving choices of - // that EC. - std::unordered_map stateToLeavingChoicesOfEndComponent; - - // Now perform the actual sampling. - std::vector> stateActionStack; - - Statistics stats; - bool convergenceCriterionMet = false; - while (!convergenceCriterionMet) { - bool result = samplePathFromState(generator, stateToIdCallback, initialStateIndex, stateActionStack, unexploredStates, unexploredMarker, terminalStates, matrix, rowGroupIndices, stateToRowGroupMapping, stateToLeavingChoicesOfEndComponent, targetStateExpression, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, stats); - - // If a terminal state was found, we update the probabilities along the path contained in the stack. - if (result) { - // Update the bounds along the path to the terminal state. - STORM_LOG_TRACE("Found terminal state, updating probabilities along path."); - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - } else { - // If not terminal state was found, the search aborted, possibly because of an EC-detection. In this - // case, we cannot update the probabilities. - STORM_LOG_TRACE("Did not find terminal state."); - } - - // Sanity check of results. - for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { - if (stateToRowGroupMapping[state] != unexploredMarker) { - STORM_LOG_ASSERT(lowerBoundsPerState[stateToRowGroupMapping[state]] <= upperBoundsPerState[stateToRowGroupMapping[state]], "The bounds for state " << state << " are not in a sane relation: " << lowerBoundsPerState[stateToRowGroupMapping[state]] << " > " << upperBoundsPerState[stateToRowGroupMapping[state]] << "."); - } - } - -// TODO: remove debug output when superfluous -// for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { -// if (stateToRowGroupMapping[state] != unexploredMarker) { -// std::cout << "state " << state << " (grp " << stateToRowGroupMapping[state] << ") has bounds [" << lowerBoundsPerState[stateToRowGroupMapping[state]] << ", " << upperBoundsPerState[stateToRowGroupMapping[state]] << "], actions: "; -// for (auto choice = rowGroupIndices[stateToRowGroupMapping[state]]; choice < rowGroupIndices[stateToRowGroupMapping[state] + 1]; ++choice) { -// std::cout << choice << " = [" << lowerBoundsPerAction[choice] << ", " << upperBoundsPerAction[choice] << "], "; -// } -// std::cout << std::endl; -// } else { -// std::cout << "state " << state << " is unexplored" << std::endl; -// } -// } - - STORM_LOG_DEBUG("Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)."); - STORM_LOG_DEBUG("Value of initial state is in [" << lowerBoundsPerState[initialStateIndex] << ", " << upperBoundsPerState[initialStateIndex] << "]."); - ValueType difference = upperBoundsPerState[initialStateIndex] - lowerBoundsPerState[initialStateIndex]; - STORM_LOG_DEBUG("Difference after iteration " << stats.iterations << " is " << difference << "."); - convergenceCriterionMet = difference < 1e-6; - - ++stats.iterations; + std::pair SparseMdpLearningModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + // TODO: take into account self-loops? + std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + for (auto const& element : explorationInformation.getRowOfMatrix(action)) { + result.first += element.getValue() * bounds.getLowerBoundForState(element.getColumn(), explorationInformation); + result.second += element.getValue() * bounds.getUpperBoundForState(element.getColumn(), explorationInformation); } - - if (storm::settings::generalSettings().isShowStatisticsSet()) { - std::cout << std::endl << "Learning summary -------------------------" << std::endl; - std::cout << "Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored, " << stats.numberOfTargetStates << " target states)" << std::endl; - std::cout << "Sampling iterations: " << stats.iterations << std::endl; - std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; + return result; + } + + template + std::pair SparseMdpLearningModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + StateType group = explorationInformation.getRowGroup(currentStateId); + std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { + std::pair actionValues = computeBoundsOfAction(action, explorationInformation, bounds); + result.first = std::max(actionValues.first, result.first); + result.second = std::max(actionValues.second, result.second); } - - return std::make_tuple(initialStateIndex, lowerBoundsPerState[initialStateIndex], upperBoundsPerState[initialStateIndex]); + return result; } template - std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { - storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); - storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); - STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); - - // Derive the expression for the target states, so we know when to abort the learning run. - storm::expressions::Expression targetStateExpression = getTargetStateExpression(subformula); - - // A container for the encountered states. - storm::storage::sparse::StateStorage stateStorage(variableInformation.getTotalBitOffset(true)); + void SparseMdpLearningModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + std::cout << "stack is:" << std::endl; + for (auto const& el : stack) { + std::cout << el.first << "-[" << el.second << "]-> "; + } + std::cout << std::endl; - // A generator used to explore the model. - storm::generator::PrismNextStateGenerator generator(program, variableInformation, false); + stack.pop_back(); + while (!stack.empty()) { + updateProbabilityOfAction(stack.back().first, stack.back().second, explorationInformation, bounds); + stack.pop_back(); + } + } + + template + void SparseMdpLearningModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + // Compute the new lower/upper values of the action. + std::pair newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds); - // A container that stores the transitions found so far. - std::vector>> matrix; + // And set them as the current value. + bounds.setBoundForAction(action, newBoundsForAction); - // A vector storing where the row group of each state starts. - std::vector rowGroupIndices; - rowGroupIndices.push_back(0); + // Check if we need to update the values for the states. + bounds.setNewLowerBoundOfStateIfGreaterThanOld(state, explorationInformation, newBoundsForAction.first); - // A vector storing the mapping from state ids to row groups. - std::vector stateToRowGroupMapping; - StateType unexploredMarker = std::numeric_limits::max(); + StateType rowGroup = explorationInformation.getRowGroup(state); + if (newBoundsForAction < bounds.getUpperBoundOfRowGroup(rowGroup)) { + + } - // A mapping of unexplored IDs to their actual compressed states. - std::unordered_map unexploredStates; + ValueType upperBound = computeUpperBoundOverAllOtherActions(state, action, explorationInformation, bounds); - // Create a callback for the next-state generator to enable it to request the index of states. - std::function stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates, &unexploredMarker] (storm::generator::CompressedState const& state) -> StateType { - StateType newIndex = stateStorage.numberOfStates; - - // Check, if the state was already registered. - std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); - - if (actualIndexBucketPair.first == newIndex) { - ++stateStorage.numberOfStates; - stateToRowGroupMapping.push_back(unexploredMarker); - unexploredStates[newIndex] = state; + uint32_t sourceRowGroup = stateToRowGroupMapping[sourceStateId]; + if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { + if (rowGroupIndices[sourceRowGroup + 1] - rowGroupIndices[sourceRowGroup] > 1) { + ValueType max = storm::utility::zero(); + + for (uint32_t currentAction = rowGroupIndices[sourceRowGroup]; currentAction < rowGroupIndices[sourceRowGroup + 1]; ++currentAction) { + std::cout << "cur: " << currentAction << std::endl; + if (currentAction == action) { + continue; + } + + ValueType currentValue = storm::utility::zero(); + for (auto const& element : transitionMatrix[currentAction]) { + currentValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); + } + max = std::max(max, currentValue); + std::cout << "max is " << max << std::endl; + } + + newUpperValue = std::max(newUpperValue, max); } - return actualIndexBucketPair.first; - }; - - // Compute and return result. - std::tuple boundsForInitialState = performLearningProcedure(targetStateExpression, stateStorage, generator, stateToIdCallback, matrix, rowGroupIndices, stateToRowGroupMapping, unexploredStates, unexploredMarker); - return std::make_unique>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); + if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { + STORM_LOG_TRACE("Got new upper bound for state " << sourceStateId << ": " << newUpperValue << " (was " << upperBoundsPerState[sourceRowGroup] << ")."); + std::cout << "writing at index " << sourceRowGroup << std::endl; + upperBoundsPerState[sourceRowGroup] = newUpperValue; + } + } } template class SparseMdpLearningModelChecker; diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 5bc8adb40..777caf2d9 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -6,7 +6,9 @@ #include "src/modelchecker/AbstractModelChecker.h" #include "src/storage/prism/Program.h" +#include "src/storage/sparse/StateStorage.h" +#include "src/generator/PrismNextStateGenerator.h" #include "src/generator/CompressedState.h" #include "src/generator/VariableInformation.h" @@ -27,53 +29,289 @@ namespace storm { } namespace modelchecker { - + template class SparseMdpLearningModelChecker : public AbstractModelChecker { public: typedef uint32_t StateType; - typedef boost::container::flat_set ChoiceSet; - typedef std::shared_ptr ChoiceSetPointer; + typedef uint32_t ActionType; + typedef boost::container::flat_set StateSet; + typedef boost::container::flat_set ActionSet; + typedef std::shared_ptr ActionSetPointer; + typedef std::vector> StateActionStack; SparseMdpLearningModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions); virtual bool canHandle(CheckTask const& checkTask) const override; - + virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; private: + // A struct that keeps track of certain statistics during the computation. struct Statistics { - Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), pathLengthUntilEndComponentDetection(27) { + Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), pathLengthUntilEndComponentDetection(27) { // Intentionally left empty. } + std::size_t iterations; std::size_t maxPathLength; std::size_t numberOfTargetStates; + std::size_t numberOfExploredStates; std::size_t pathLengthUntilEndComponentDetection; }; - - bool exploreState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType const& currentStateId, storm::generator::CompressedState const& compressedState, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats); - bool samplePathFromState(storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, StateType initialStateIndex, std::vector>& stateActionStack, std::unordered_map& unexploredStates, StateType const& unexploredMarker, boost::container::flat_set& terminalStates, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& stateToLeavingChoicesOfEndComponent, storm::expressions::Expression const& targetStateExpression, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, Statistics& stats); + // A struct containing the data required for state exploration. + struct StateGeneration { + StateGeneration(storm::generator::PrismNextStateGenerator&& generator, storm::expressions::Expression const& targetStateExpression) : generator(std::move(generator)), targetStateExpression(targetStateExpression) { + // Intentionally left empty. + } + + std::vector getInitialStates() { + return generator.getInitialStates(stateToIdCallback); + } + + storm::generator::StateBehavior expand() { + return generator.expand(stateToIdCallback); + } + + bool isTargetState() const { + return generator.satisfies(targetStateExpression); + } + + storm::generator::PrismNextStateGenerator generator; + std::function stateToIdCallback; + storm::expressions::Expression targetStateExpression; + }; + + // A structure containing the data assembled during exploration. + struct ExplorationInformation { + ExplorationInformation(uint_fast64_t bitsPerBucket, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker) { + // Intentionally left empty. + } + + storm::storage::sparse::StateStorage stateStorage; + + std::vector>> matrix; + std::vector rowGroupIndices; + + std::vector stateToRowGroupMapping; + StateType unexploredMarker; + std::unordered_map unexploredStates; + + storm::OptimizationDirection optimizationDirection; + StateSet terminalStates; + + std::unordered_map stateToLeavingChoicesOfEndComponent; + + void setInitialStates(std::vector const& initialStates) { + stateStorage.initialStateIndices = initialStates; + } + + StateType getFirstInitialState() const { + return stateStorage.initialStateIndices.front(); + } + + std::size_t getNumberOfInitialStates() const { + return stateStorage.initialStateIndices.size(); + } + + void addUnexploredState(storm::generator::CompressedState const& compressedState) { + stateToRowGroupMapping.push_back(unexploredMarker); + unexploredStates[stateStorage.numberOfStates] = compressedState; + ++stateStorage.numberOfStates; + } + + void assignStateToRowGroup(StateType const& state, ActionType const& rowGroup) { + stateToRowGroupMapping[state] = rowGroup; + } + + void assignStateToNextRowGroup(StateType const& state) { + stateToRowGroupMapping[state] = rowGroupIndices.size() - 1; + } + + void newRowGroup(ActionType const& action) { + rowGroupIndices.push_back(action); + } + + void newRowGroup() { + newRowGroup(matrix.size()); + } + + std::size_t getNumberOfUnexploredStates() const { + return unexploredStates.size(); + } + + std::size_t getNumberOfDiscoveredStates() const { + return stateStorage.numberOfStates; + } + + StateType const& getRowGroup(StateType const& state) const { + return stateToRowGroupMapping[state]; + } + + StateType const& getUnexploredMarker() const { + return unexploredMarker; + } + + bool isUnexplored(StateType const& state) const { + return unexploredStates.find(state) == unexploredStates.end(); + } + + bool isTerminal(StateType const& state) const { + return terminalStates.find(state) != terminalStates.end(); + } + + ActionType const& getStartRowOfGroup(StateType const& group) const { + return rowGroupIndices[group]; + } + + void addTerminalState(StateType const& state) { + terminalStates.insert(state); + } + + std::vector>& getRowOfMatrix(ActionType const& row) { + return matrix[row]; + } + + std::vector> const& getRowOfMatrix(ActionType const& row) const { + return matrix[row]; + } + + void addRowsToMatrix(std::size_t const& count) { + matrix.resize(matrix.size() + count); + } + }; + + // A struct containg the lower and upper bounds per state and action. + struct BoundValues { + std::vector lowerBoundsPerState; + std::vector upperBoundsPerState; + std::vector lowerBoundsPerAction; + std::vector upperBoundsPerAction; + + std::pair getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { + ActionType index = explorationInformation.getRowGroup(state); + if (index == explorationInformation.getUnexploredMarker()) { + return std::make_pair(storm::utility::zero(), storm::utility::one()); + } 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(); + } else { + return getLowerBoundForRowGroup(index, explorationInformation); + } + } + + ValueType getLowerBoundForRowGroup(StateType const& rowGroup, ExplorationInformation const& explorationInformation) 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(); + } else { + return getUpperBoundForRowGroup(index, explorationInformation); + } + } + + ValueType getUpperBoundForRowGroup(StateType const& rowGroup, ExplorationInformation const& explorationInformation) const { + return upperBoundsPerState[rowGroup]; + } + + std::pair 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 getDifferenceOfStateBounds(StateType const& state, ExplorationInformation const& explorationInformation) { + std::pair bounds = getBoundsForState(state, explorationInformation); + return bounds.second - bounds.first; + } + + void initializeStateBoundsForNextState(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { + lowerBoundsPerState.push_back(vals.first); + upperBoundsPerState.push_back(vals.second); + } + + void initializeActionBoundsForNextAction(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { + lowerBoundsPerAction.push_back(vals.first); + upperBoundsPerAction.push_back(vals.second); + } + + void setLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value) { + lowerBoundsPerState[explorationInformation.getRowGroup(state)] = value; + } + + void setUpperBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value) { + upperBoundsPerState[explorationInformation.getRowGroup(state)] = value; + } + + void setBoundsForAction(ActionType const& action, std::pair const& values) { + lowerBoundsPerAction[action] = values.first; + upperBoundsPerAction[action] = values.second; + } + + void setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { + StateType const& rowGroup = explorationInformation.getRowGroup(state); + lowerBoundsPerState[rowGroup] = values.first; + upperBoundsPerState[rowGroup] = values.second; + } + + bool setNewLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newLowerValue) { + StateType const& rowGroup = explorationInformation.getRowGroup(state); + if (lowerBoundsPerState[rowGroup] < newLowerValue) { + lowerBoundsPerState[rowGroup] = newLowerValue; + } + } + + bool setNewUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newUpperValue) { + StateType const& rowGroup = explorationInformation.getRowGroup(state); + if (newUpperValue < upperBoundsPerState[rowGroup]) { + upperBoundsPerState[rowGroup] = newUpperValue; + } + } + }; - std::tuple performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage& stateStorage, storm::generator::PrismNextStateGenerator& generator, std::function const& stateToIdCallback, std::vector>>& matrix, std::vector& rowGroupIndices, std::vector& stateToRowGroupMapping, std::unordered_map& unexploredStates, StateType const& unexploredMarker); + storm::expressions::Expression getTargetStateExpression(storm::logic::Formula const& subformula) const; - void updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; + std::function createStateToIdCallback(ExplorationInformation& explorationInformation) const; - void updateProbabilitiesUsingStack(std::vector>& stateActionStack, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBounds, std::vector& upperBounds, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, StateType const& unexploredMarker) const; + std::tuple performLearningProcedure(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const; + + bool samplePathFromState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const; - uint32_t sampleFromMaxActions(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& upperBoundsPerState, std::unordered_map const& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker); + bool exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; - StateType sampleSuccessorFromAction(StateType chosenAction, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping); + uint32_t sampleMaxAction(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; + + StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation) const; - void detectEndComponents(std::vector> const& stateActionStack, boost::container::flat_set& terminalStates, std::vector>>& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector& lowerBoundsPerAction, std::vector& upperBoundsPerAction, std::vector& lowerBoundsPerState, std::vector& upperBoundsPerState, std::unordered_map& stateToLeavingChoicesOfEndComponent, StateType const& unexploredMarker) const; + void detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds) const; - std::pair computeValuesOfChoice(uint32_t action, std::vector>> const& transitionMatrix, std::vector const& stateToRowGroupMapping, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker); + void updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; + + void updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; - std::pair computeValuesOfState(StateType currentStateId, std::vector>> const& transitionMatrix, std::vector const& rowGroupIndices, std::vector const& stateToRowGroupMapping, std::vector const& lowerBounds, std::vector const& upperBounds, std::vector const& lowerBoundsPerState, std::vector const& upperBoundsPerState, StateType const& unexploredMarker); + std::pair computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + std::pair computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + ValueType computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + ValueType computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + ValueType computeLowerBoundOfState(StateType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + ValueType computeUpperBoundOfState(StateType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - storm::expressions::Expression getTargetStateExpression(storm::logic::Formula const& subformula); - // The program that defines the model to check. storm::prism::Program program; From 1405cdfc462053cfc0858bfa6f81fa4660953716 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 5 Apr 2016 21:17:18 +0200 Subject: [PATCH 16/31] debugged the refactoring a bit Former-commit-id: 9df3d5d5330eba4c156f5c991ef37f06c3923188 --- src/generator/NextStateGenerator.h | 2 +- src/generator/PrismNextStateGenerator.cpp | 4 +- src/generator/PrismNextStateGenerator.h | 4 +- .../SparseMdpLearningModelChecker.cpp | 139 +++++++++--------- .../SparseMdpLearningModelChecker.h | 26 +++- 5 files changed, 89 insertions(+), 86 deletions(-) diff --git a/src/generator/NextStateGenerator.h b/src/generator/NextStateGenerator.h index de80bc83d..7dab9f470 100644 --- a/src/generator/NextStateGenerator.h +++ b/src/generator/NextStateGenerator.h @@ -21,7 +21,7 @@ namespace storm { virtual void load(CompressedState const& state) = 0; virtual StateBehavior expand(StateToIdCallback const& stateToIdCallback) = 0; - virtual bool satisfies(storm::expressions::Expression const& expression) = 0; + virtual bool satisfies(storm::expressions::Expression const& expression) const = 0; }; } } diff --git a/src/generator/PrismNextStateGenerator.cpp b/src/generator/PrismNextStateGenerator.cpp index 83fd4b6c8..0064636b6 100644 --- a/src/generator/PrismNextStateGenerator.cpp +++ b/src/generator/PrismNextStateGenerator.cpp @@ -13,7 +13,7 @@ namespace storm { PrismNextStateGenerator::PrismNextStateGenerator(storm::prism::Program const& program, VariableInformation const& variableInformation, bool buildChoiceLabeling) : program(program), selectedRewardModels(), buildChoiceLabeling(buildChoiceLabeling), variableInformation(variableInformation), evaluator(program.getManager()), state(nullptr), comparator() { // Intentionally left empty. } - + template void PrismNextStateGenerator::addRewardModel(storm::prism::RewardModel const& rewardModel) { selectedRewardModels.push_back(rewardModel); @@ -58,7 +58,7 @@ namespace storm { } template - bool PrismNextStateGenerator::satisfies(storm::expressions::Expression const& expression) { + bool PrismNextStateGenerator::satisfies(storm::expressions::Expression const& expression) const { return evaluator.asBool(expression); } diff --git a/src/generator/PrismNextStateGenerator.h b/src/generator/PrismNextStateGenerator.h index 4eebf0dd3..fe997d73a 100644 --- a/src/generator/PrismNextStateGenerator.h +++ b/src/generator/PrismNextStateGenerator.h @@ -18,7 +18,7 @@ namespace storm { typedef typename NextStateGenerator::StateToIdCallback StateToIdCallback; PrismNextStateGenerator(storm::prism::Program const& program, VariableInformation const& variableInformation, bool buildChoiceLabeling); - + /*! * Adds a reward model to the list of selected reward models () */ @@ -34,7 +34,7 @@ namespace storm { virtual void load(CompressedState const& state) override; virtual StateBehavior expand(StateToIdCallback const& stateToIdCallback) override; - virtual bool satisfies(storm::expressions::Expression const& expression) override; + virtual bool satisfies(storm::expressions::Expression const& expression) const override; private: /*! diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 67d3c6476..a762618f8 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -40,7 +40,7 @@ namespace storm { STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "For nondeterministic systems, an optimization direction (min/max) must be given in the property."); STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); - StateGeneration stateGeneration(storm::generator::PrismNextStateGenerator(program, variableInformation, false), getTargetStateExpression(subformula)); + StateGeneration stateGeneration(program, variableInformation, getTargetStateExpression(subformula)); ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true)); explorationInformation.optimizationDirection = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; @@ -49,7 +49,7 @@ namespace storm { explorationInformation.newRowGroup(0); // Create a callback for the next-state generator to enable it to request the index of states. - std::function stateToIdCallback = createStateToIdCallback(explorationInformation); + stateGeneration.stateToIdCallback = createStateToIdCallback(explorationInformation); // Compute and return result. std::tuple boundsForInitialState = performLearningProcedure(stateGeneration, explorationInformation); @@ -114,7 +114,7 @@ namespace storm { STORM_LOG_TRACE("Did not find terminal state."); } - STORM_LOG_DEBUG("Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << "explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored)."); + STORM_LOG_DEBUG("Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored)."); STORM_LOG_DEBUG("Value of initial state is in [" << bounds.getLowerBoundForState(initialStateIndex, explorationInformation) << ", " << bounds.getUpperBoundForState(initialStateIndex, explorationInformation) << "]."); ValueType difference = bounds.getDifferenceOfStateBounds(initialStateIndex, explorationInformation); STORM_LOG_DEBUG("Difference after iteration " << stats.iterations << " is " << difference << "."); @@ -125,7 +125,7 @@ namespace storm { if (storm::settings::generalSettings().isShowStatisticsSet()) { std::cout << std::endl << "Learning summary -------------------------" << std::endl; - std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << "explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target states)" << std::endl; + std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target states)" << std::endl; std::cout << "Sampling iterations: " << stats.iterations << std::endl; std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; } @@ -165,7 +165,7 @@ namespace storm { if (!foundTerminalState) { // At this point, we can be sure that the state was expanded and that we can sample according to the // probabilities in the matrix. - uint32_t chosenAction = sampleFromMaxActions(currentStateId, explorationInformation, bounds); + uint32_t chosenAction = sampleMaxAction(currentStateId, explorationInformation, bounds); stack.back().second = chosenAction; STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); @@ -194,10 +194,19 @@ namespace storm { template bool SparseMdpLearningModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { - bool isTerminalState = false; bool isTargetState = false; + ++stats.numberOfExploredStates; + + // Finally, map the unexplored state to the row group. + explorationInformation.assignStateToNextRowGroup(currentStateId); + STORM_LOG_TRACE("Assigning row group " << explorationInformation.getRowGroup(currentStateId) << " to state " << currentStateId << "."); + + // Initialize the bounds, because some of the following computations depend on the values to be available for + // all states that have been assigned to a row-group. + bounds.initializeBoundsForNextState(); + // Before generating the behavior of the state, we need to determine whether it's a target state that // does not need to be expanded. stateGeneration.generator.load(currentState); @@ -232,38 +241,41 @@ namespace storm { StateType startRow = explorationInformation.matrix.size(); explorationInformation.addRowsToMatrix(behavior.getNumberOfChoices()); - // Terminate the row group. - explorationInformation.rowGroupIndices.push_back(explorationInformation.matrix.size()); - ActionType currentAction = 0; + std::pair stateBounds(storm::utility::zero(), storm::utility::zero()); + for (auto const& choice : behavior) { for (auto const& entry : choice) { - std::cout << "adding " << currentStateId << " -> " << entry.first << " with prob " << entry.second << std::endl; - explorationInformation.matrix[startRow + currentAction].emplace_back(entry.first, entry.second); + explorationInformation.getRowOfMatrix(startRow + currentAction).emplace_back(entry.first, entry.second); } - bounds.initializeActionBoundsForNextAction(computeBoundsOfAction(startRow + currentAction, explorationInformation, bounds)); + std::pair actionBounds = computeBoundsOfAction(startRow + currentAction, explorationInformation, bounds); + bounds.initializeBoundsForNextAction(actionBounds); + stateBounds = std::make_pair(std::max(stateBounds.first, actionBounds.first), std::max(stateBounds.second, actionBounds.second)); STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << bounds.getLowerBoundForAction(startRow + currentAction) << " and " << bounds.getUpperBoundForAction(startRow + currentAction) << "."); ++currentAction; } - bounds.initializeStateBoundsForNextState(computeBoundsOfState(currentStateId, explorationInformation, bounds)); - STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << bounds.getLowerBoundForState(currentStateId) << " and " << bounds.getUpperBoundForState(currentStateId) << "."); + // Terminate the row group. + explorationInformation.rowGroupIndices.push_back(explorationInformation.matrix.size()); + + bounds.setBoundsForState(currentStateId, explorationInformation, stateBounds); + STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << bounds.getLowerBoundForState(currentStateId, explorationInformation) << " and " << bounds.getUpperBoundForState(currentStateId, explorationInformation) << "."); } } - + if (isTerminalState) { STORM_LOG_TRACE("State does not need to be explored, because it is " << (isTargetState ? "a target state" : "a rejecting terminal state") << "."); explorationInformation.addTerminalState(currentStateId); if (isTargetState) { - bounds.initializeStateBoundsForNextState(std::make_pair(storm::utility::one(), storm::utility::one())); - bounds.initializeStateBoundsForNextAction(std::make_pair(storm::utility::one(), storm::utility::one())); + bounds.setBoundsForState(currentStateId, explorationInformation, std::make_pair(storm::utility::one(), storm::utility::one())); + bounds.initializeBoundsForNextAction(std::make_pair(storm::utility::one(), storm::utility::one())); } else { - bounds.initializeStateBoundsForNextState(std::make_pair(storm::utility::zero(), storm::utility::zero())); - bounds.initializeStateBoundsForNextAction(std::make_pair(storm::utility::zero(), storm::utility::zero())); + bounds.setBoundsForState(currentStateId, explorationInformation, std::make_pair(storm::utility::zero(), storm::utility::zero())); + bounds.initializeBoundsForNextAction(std::make_pair(storm::utility::zero(), storm::utility::zero())); } // Increase the size of the matrix, but leave the row empty. @@ -273,10 +285,6 @@ namespace storm { explorationInformation.newRowGroup(); } - // Finally, map the unexplored state to the row group. - explorationInformation.assignStateToNextRowGroup(currentStateId); - STORM_LOG_TRACE("Assigning row group " << explorationInformation.getRowGroup(currentStateId) << " to state " << currentStateId << "."); - return isTerminalState; } @@ -303,11 +311,17 @@ namespace storm { } } - std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return b.second > a.second; } ); - auto end = std::equal_range(actionValues.begin(), actionValues.end(), [this] (std::pair const& a, std::pair const& b) { return comparator.isEqual(a.second, b.second); } ); + STORM_LOG_ASSERT(!actionValues.empty(), "Values for actions must not be empty."); + + std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return a.second > b.second; } ); + + auto end = ++actionValues.begin(); + while (end != actionValues.end() && comparator.isEqual(actionValues.begin()->second, end->second)) { + ++end; + } // Now sample from all maximizing actions. - std::uniform_int_distribution distribution(0, std::distance(actionValues.begin(), end)); + std::uniform_int_distribution distribution(0, std::distance(actionValues.begin(), end) - 1); return actionValues[distribution(randomGenerator)].first; } @@ -350,7 +364,6 @@ namespace storm { // Create a mapping for faster look-up during the translation of flexible matrix to the real sparse matrix. std::unordered_map relevantStateToNewRowGroupMapping; for (StateType index = 0; index < relevantStates.size(); ++index) { - std::cout << "relevant: " << relevantStates[index] << std::endl; relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); } @@ -400,11 +413,8 @@ namespace storm { ActionSetPointer leavingChoices = std::make_shared(); for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. - std::cout << "local state: " << stateAndChoices.first << std::endl; StateType originalState = relevantStates[stateAndChoices.first]; - std::cout << "original state: " << originalState << std::endl; uint32_t originalRowGroup = explorationInformation.getRowGroup(originalState); - std::cout << "original row group: " << originalRowGroup << std::endl; // TODO: This checks for a target state is a bit hackish and only works for max probabilities. if (!containsTargetState && comparator.isOne(bounds.getLowerBoundForRowGroup(originalRowGroup, explorationInformation))) { @@ -439,7 +449,7 @@ namespace storm { STORM_LOG_TRACE("MEC contains a target state."); for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. - StateType originalState = relevantStates[stateAndChoices.first]; + StateType const& originalState = relevantStates[stateAndChoices.first]; STORM_LOG_TRACE("Setting lower bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 1."); bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one()); @@ -450,7 +460,7 @@ namespace storm { // If there is no choice leaving the EC, but it contains no target state, all states have probability 0. for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. - StateType originalState = relevantStates[stateAndChoices.first]; + StateType const& originalState = relevantStates[stateAndChoices.first]; STORM_LOG_TRACE("Setting upper bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 0."); bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); @@ -481,7 +491,7 @@ namespace storm { template ValueType SparseMdpLearningModelChecker::computeLowerBoundOfState(StateType const& state, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { StateType group = explorationInformation.getRowGroup(state); - ValueType result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + ValueType result = storm::utility::zero(); for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { ValueType actionValue = computeLowerBoundOfAction(action, explorationInformation, bounds); result = std::max(actionValue, result); @@ -492,7 +502,7 @@ namespace storm { template ValueType SparseMdpLearningModelChecker::computeUpperBoundOfState(StateType const& state, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { StateType group = explorationInformation.getRowGroup(state); - ValueType result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + ValueType result = storm::utility::zero(); for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { ValueType actionValue = computeUpperBoundOfAction(action, explorationInformation, bounds); result = std::max(actionValue, result); @@ -525,12 +535,6 @@ namespace storm { template void SparseMdpLearningModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { - std::cout << "stack is:" << std::endl; - for (auto const& el : stack) { - std::cout << el.first << "-[" << el.second << "]-> "; - } - std::cout << std::endl; - stack.pop_back(); while (!stack.empty()) { updateProbabilityOfAction(stack.back().first, stack.back().second, explorationInformation, bounds); @@ -538,51 +542,40 @@ namespace storm { } } + template + ValueType SparseMdpLearningModelChecker::computeUpperBoundOverAllOtherActions(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType max = storm::utility::zero(); + + ActionType group = explorationInformation.getRowGroup(state); + for (auto currentAction = explorationInformation.getStartRowOfGroup(group); currentAction < explorationInformation.getStartRowOfGroup(group + 1); ++currentAction) { + if (currentAction == action) { + continue; + } + + max = std::max(max, computeUpperBoundOfAction(currentAction, explorationInformation, bounds)); + } + + return max; + } + template void SparseMdpLearningModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { // Compute the new lower/upper values of the action. std::pair newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds); // And set them as the current value. - bounds.setBoundForAction(action, newBoundsForAction); + bounds.setBoundsForAction(action, newBoundsForAction); // Check if we need to update the values for the states. bounds.setNewLowerBoundOfStateIfGreaterThanOld(state, explorationInformation, newBoundsForAction.first); StateType rowGroup = explorationInformation.getRowGroup(state); - if (newBoundsForAction < bounds.getUpperBoundOfRowGroup(rowGroup)) { - - } - - ValueType upperBound = computeUpperBoundOverAllOtherActions(state, action, explorationInformation, bounds); - - uint32_t sourceRowGroup = stateToRowGroupMapping[sourceStateId]; - if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { - if (rowGroupIndices[sourceRowGroup + 1] - rowGroupIndices[sourceRowGroup] > 1) { - ValueType max = storm::utility::zero(); - - for (uint32_t currentAction = rowGroupIndices[sourceRowGroup]; currentAction < rowGroupIndices[sourceRowGroup + 1]; ++currentAction) { - std::cout << "cur: " << currentAction << std::endl; - if (currentAction == action) { - continue; - } - - ValueType currentValue = storm::utility::zero(); - for (auto const& element : transitionMatrix[currentAction]) { - currentValue += element.getValue() * (stateToRowGroupMapping[element.getColumn()] == unexploredMarker ? storm::utility::one() : upperBoundsPerState[stateToRowGroupMapping[element.getColumn()]]); - } - max = std::max(max, currentValue); - std::cout << "max is " << max << std::endl; - } - - newUpperValue = std::max(newUpperValue, max); - } - - if (newUpperValue < upperBoundsPerState[sourceRowGroup]) { - STORM_LOG_TRACE("Got new upper bound for state " << sourceStateId << ": " << newUpperValue << " (was " << upperBoundsPerState[sourceRowGroup] << ")."); - std::cout << "writing at index " << sourceRowGroup << std::endl; - upperBoundsPerState[sourceRowGroup] = newUpperValue; + if (newBoundsForAction.second < bounds.getUpperBoundForRowGroup(rowGroup)) { + if (explorationInformation.getRowGroupSize(rowGroup) > 1) { + newBoundsForAction.second = std::max(newBoundsForAction.second, computeUpperBoundOverAllOtherActions(state, action, explorationInformation, bounds)); } + + bounds.setUpperBoundForState(state, explorationInformation, newBoundsForAction.second); } } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 777caf2d9..01ea2d323 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -62,7 +62,7 @@ namespace storm { // A struct containing the data required for state exploration. struct StateGeneration { - StateGeneration(storm::generator::PrismNextStateGenerator&& generator, storm::expressions::Expression const& targetStateExpression) : generator(std::move(generator)), targetStateExpression(targetStateExpression) { + StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, storm::expressions::Expression const& targetStateExpression) : generator(program, variableInformation, false), targetStateExpression(targetStateExpression) { // Intentionally left empty. } @@ -125,8 +125,9 @@ namespace storm { stateToRowGroupMapping[state] = rowGroup; } - void assignStateToNextRowGroup(StateType const& state) { + StateType assignStateToNextRowGroup(StateType const& state) { stateToRowGroupMapping[state] = rowGroupIndices.size() - 1; + return stateToRowGroupMapping[state]; } void newRowGroup(ActionType const& action) { @@ -154,7 +155,7 @@ namespace storm { } bool isUnexplored(StateType const& state) const { - return unexploredStates.find(state) == unexploredStates.end(); + return stateToRowGroupMapping[state] == unexploredMarker; } bool isTerminal(StateType const& state) const { @@ -165,6 +166,10 @@ namespace storm { return rowGroupIndices[group]; } + std::size_t getRowGroupSize(StateType const& group) const { + return rowGroupIndices[group + 1] - rowGroupIndices[group]; + } + void addTerminalState(StateType const& state) { terminalStates.insert(state); } @@ -216,11 +221,11 @@ namespace storm { if (index == explorationInformation.getUnexploredMarker()) { return storm::utility::one(); } else { - return getUpperBoundForRowGroup(index, explorationInformation); + return getUpperBoundForRowGroup(index); } } - ValueType getUpperBoundForRowGroup(StateType const& rowGroup, ExplorationInformation const& explorationInformation) const { + ValueType const& getUpperBoundForRowGroup(StateType const& rowGroup) const { return upperBoundsPerState[rowGroup]; } @@ -241,12 +246,12 @@ namespace storm { return bounds.second - bounds.first; } - void initializeStateBoundsForNextState(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { + void initializeBoundsForNextState(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { lowerBoundsPerState.push_back(vals.first); upperBoundsPerState.push_back(vals.second); } - void initializeActionBoundsForNextAction(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { + void initializeBoundsForNextAction(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { lowerBoundsPerAction.push_back(vals.first); upperBoundsPerAction.push_back(vals.second); } @@ -274,14 +279,18 @@ namespace storm { StateType const& rowGroup = explorationInformation.getRowGroup(state); if (lowerBoundsPerState[rowGroup] < newLowerValue) { lowerBoundsPerState[rowGroup] = newLowerValue; + return true; } + return false; } bool setNewUpperBoundOfStateIfLessThanOld(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; } }; @@ -306,6 +315,7 @@ namespace storm { void updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; std::pair computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + ValueType computeUpperBoundOverAllOtherActions(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; std::pair computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; ValueType computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; ValueType computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; @@ -319,7 +329,7 @@ namespace storm { storm::generator::VariableInformation variableInformation; // The random number generator. - std::default_random_engine randomGenerator; + mutable std::default_random_engine randomGenerator; // A comparator used to determine whether values are equal. storm::utility::ConstantsComparator comparator; From c2b287a1e174bca5eb32c9ba3c55b200fa5e234f Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 6 Apr 2016 16:05:54 +0200 Subject: [PATCH 17/31] more work on learning approach Former-commit-id: 48aa9ddd2cbe7ea50a2a9214f4fdff41163f8e05 --- src/logic/AtomicLabelFormula.cpp | 1 + src/logic/BinaryBooleanStateFormula.h | 2 +- src/logic/BooleanLiteralFormula.h | 2 +- src/logic/BoundedUntilFormula.h | 2 +- src/logic/CloneVisitor.h | 37 ++++ src/logic/ConditionalFormula.cpp | 4 + src/logic/ConditionalFormula.h | 7 +- src/logic/CopyVisitor.cpp | 106 +++++++++++ src/logic/CumulativeRewardFormula.h | 2 +- src/logic/EventuallyFormula.cpp | 4 + src/logic/EventuallyFormula.h | 2 + src/logic/Formula.cpp | 12 ++ src/logic/Formula.h | 9 + src/logic/LabelSubstitutionVisitor.cpp | 27 +++ src/logic/LabelSubstitutionVisitor.h | 29 +++ src/logic/OperatorFormula.cpp | 4 + src/logic/OperatorFormula.h | 4 +- src/logic/ToExpressionVisitor.cpp | 103 +++++++++++ src/logic/ToExpressionVisitor.h | 39 ++++ .../SparseMdpLearningModelChecker.cpp | 167 ++++++++++-------- .../SparseMdpLearningModelChecker.h | 34 ++-- src/storage/prism/Program.cpp | 8 + src/storage/prism/Program.h | 7 + 23 files changed, 515 insertions(+), 97 deletions(-) create mode 100644 src/logic/CloneVisitor.h create mode 100644 src/logic/CopyVisitor.cpp create mode 100644 src/logic/LabelSubstitutionVisitor.cpp create mode 100644 src/logic/LabelSubstitutionVisitor.h create mode 100644 src/logic/ToExpressionVisitor.cpp create mode 100644 src/logic/ToExpressionVisitor.h diff --git a/src/logic/AtomicLabelFormula.cpp b/src/logic/AtomicLabelFormula.cpp index 19a5f673f..292351c5d 100644 --- a/src/logic/AtomicLabelFormula.cpp +++ b/src/logic/AtomicLabelFormula.cpp @@ -1,5 +1,6 @@ #include "src/logic/AtomicLabelFormula.h" +#include "src/logic/AtomicExpressionFormula.h" #include "src/logic/FormulaVisitor.h" namespace storm { diff --git a/src/logic/BinaryBooleanStateFormula.h b/src/logic/BinaryBooleanStateFormula.h index 892d28bc9..c0880d058 100644 --- a/src/logic/BinaryBooleanStateFormula.h +++ b/src/logic/BinaryBooleanStateFormula.h @@ -29,7 +29,7 @@ namespace storm { virtual std::ostream& writeToStream(std::ostream& out) const override; virtual std::shared_ptr substitute(std::map const& substitution) const override; - + private: OperatorType operatorType; }; diff --git a/src/logic/BooleanLiteralFormula.h b/src/logic/BooleanLiteralFormula.h index 405044600..955d1dbbe 100644 --- a/src/logic/BooleanLiteralFormula.h +++ b/src/logic/BooleanLiteralFormula.h @@ -20,7 +20,7 @@ namespace storm { virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; virtual std::shared_ptr substitute(std::map const& substitution) const override; - + virtual std::ostream& writeToStream(std::ostream& out) const override; private: diff --git a/src/logic/BoundedUntilFormula.h b/src/logic/BoundedUntilFormula.h index 222bab142..0e18633ce 100644 --- a/src/logic/BoundedUntilFormula.h +++ b/src/logic/BoundedUntilFormula.h @@ -27,7 +27,7 @@ namespace storm { virtual std::ostream& writeToStream(std::ostream& out) const override; virtual std::shared_ptr substitute(std::map const& substitution) const override; - + private: boost::variant> bounds; }; diff --git a/src/logic/CloneVisitor.h b/src/logic/CloneVisitor.h new file mode 100644 index 000000000..a23ee4a8b --- /dev/null +++ b/src/logic/CloneVisitor.h @@ -0,0 +1,37 @@ +#ifndef STORM_LOGIC_CLONEVISITOR_H_ +#define STORM_LOGIC_CLONEVISITOR_H_ + +#include "src/logic/FormulaVisitor.h" + +namespace storm { + namespace logic { + + class CloneVisitor : public FormulaVisitor { + public: + std::shared_ptr clone(Formula const& f) const; + + virtual boost::any visit(AtomicExpressionFormula const& f, boost::any const& data) const override; + virtual boost::any visit(AtomicLabelFormula const& f, boost::any const& data) const override; + virtual boost::any visit(BinaryBooleanStateFormula const& f, boost::any const& data) const override; + virtual boost::any visit(BooleanLiteralFormula const& f, boost::any const& data) const override; + virtual boost::any visit(BoundedUntilFormula const& f, boost::any const& data) const override; + virtual boost::any visit(ConditionalFormula const& f, boost::any const& data) const override; + virtual boost::any visit(CumulativeRewardFormula const& f, boost::any const& data) const override; + virtual boost::any visit(EventuallyFormula const& f, boost::any const& data) const override; + virtual boost::any visit(TimeOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(GloballyFormula const& f, boost::any const& data) const override; + virtual boost::any visit(InstantaneousRewardFormula const& f, boost::any const& data) const override; + virtual boost::any visit(LongRunAverageOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(LongRunAverageRewardFormula const& f, boost::any const& data) const override; + virtual boost::any visit(NextFormula const& f, boost::any const& data) const override; + virtual boost::any visit(ProbabilityOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(RewardOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(UnaryBooleanStateFormula const& f, boost::any const& data) const override; + virtual boost::any visit(UntilFormula const& f, boost::any const& data) const override; + }; + + } +} + + +#endif /* STORM_LOGIC_CLONEVISITOR_H_ */ \ No newline at end of file diff --git a/src/logic/ConditionalFormula.cpp b/src/logic/ConditionalFormula.cpp index b12b831be..55bddabd9 100644 --- a/src/logic/ConditionalFormula.cpp +++ b/src/logic/ConditionalFormula.cpp @@ -18,6 +18,10 @@ namespace storm { return *conditionFormula; } + FormulaContext const& ConditionalFormula::getContext() const { + return context; + } + bool ConditionalFormula::isConditionalProbabilityFormula() const { return context == FormulaContext::Probability; } diff --git a/src/logic/ConditionalFormula.h b/src/logic/ConditionalFormula.h index 66d71ebba..87303198c 100644 --- a/src/logic/ConditionalFormula.h +++ b/src/logic/ConditionalFormula.h @@ -7,9 +7,7 @@ namespace storm { namespace logic { class ConditionalFormula : public Formula { - public: - enum class Context { Probability, Reward }; - + public: ConditionalFormula(std::shared_ptr const& subformula, std::shared_ptr const& conditionFormula, FormulaContext context = FormulaContext::Probability); virtual ~ConditionalFormula() { @@ -18,6 +16,7 @@ namespace storm { Formula const& getSubformula() const; Formula const& getConditionFormula() const; + FormulaContext const& getContext() const; virtual bool isConditionalProbabilityFormula() const override; virtual bool isConditionalRewardFormula() const override; @@ -27,7 +26,7 @@ namespace storm { virtual std::ostream& writeToStream(std::ostream& out) const override; virtual std::shared_ptr substitute(std::map const& substitution) const override; - + virtual void gatherAtomicExpressionFormulas(std::vector>& atomicExpressionFormulas) const override; virtual void gatherAtomicLabelFormulas(std::vector>& atomicLabelFormulas) const override; virtual void gatherReferencedRewardModels(std::set& referencedRewardModels) const override; diff --git a/src/logic/CopyVisitor.cpp b/src/logic/CopyVisitor.cpp new file mode 100644 index 000000000..fe7182bf7 --- /dev/null +++ b/src/logic/CopyVisitor.cpp @@ -0,0 +1,106 @@ +#include "src/logic/CloneVisitor.h" + +#include "src/logic/Formulas.h" + +namespace storm { + namespace logic { + + std::shared_ptr CloneVisitor::clone(Formula const& f) const { + boost::any result = f.accept(*this, boost::any()); + return boost::any_cast>(result); + } + + boost::any CloneVisitor::visit(AtomicExpressionFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f)); + } + + boost::any CloneVisitor::visit(AtomicLabelFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f)); + } + + boost::any CloneVisitor::visit(BinaryBooleanStateFormula const& f, boost::any const& data) const { + std::shared_ptr left = boost::any_cast>(f.getLeftSubformula().accept(*this, data)); + std::shared_ptr right = boost::any_cast>(f.getRightSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(f.getOperator(), left, right)); + } + + boost::any CloneVisitor::visit(BooleanLiteralFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f)); + } + + boost::any CloneVisitor::visit(BoundedUntilFormula const& f, boost::any const& data) const { + std::shared_ptr left = boost::any_cast>(f.getLeftSubformula().accept(*this, data)); + std::shared_ptr right = boost::any_cast>(f.getRightSubformula().accept(*this, data)); + if (f.hasDiscreteTimeBound()) { + return std::static_pointer_cast(std::make_shared(left, right, f.getDiscreteTimeBound())); + } else { + return std::static_pointer_cast(std::make_shared(left, right, f.getIntervalBounds())); + } + } + + boost::any CloneVisitor::visit(ConditionalFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + std::shared_ptr conditionFormula = boost::any_cast>(f.getConditionFormula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula, conditionFormula, f.getContext())); + } + + boost::any CloneVisitor::visit(CumulativeRewardFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f)); + } + + boost::any CloneVisitor::visit(EventuallyFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula, f.getContext())); + } + + boost::any CloneVisitor::visit(TimeOperatorFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula, f.getOperatorInformation())); + } + + boost::any CloneVisitor::visit(GloballyFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula)); + } + + boost::any CloneVisitor::visit(InstantaneousRewardFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f)); + } + + boost::any CloneVisitor::visit(LongRunAverageOperatorFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula, f.getOperatorInformation())); + } + + boost::any CloneVisitor::visit(LongRunAverageRewardFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f)); + } + + boost::any CloneVisitor::visit(NextFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula)); + } + + boost::any CloneVisitor::visit(ProbabilityOperatorFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula, f.getOperatorInformation())); + } + + boost::any CloneVisitor::visit(RewardOperatorFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(subformula, f.getOptionalRewardModelName(), f.getOperatorInformation())); + } + + boost::any CloneVisitor::visit(UnaryBooleanStateFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(f.getOperator(), subformula)); + } + + boost::any CloneVisitor::visit(UntilFormula const& f, boost::any const& data) const { + std::shared_ptr left = boost::any_cast>(f.getLeftSubformula().accept(*this, data)); + std::shared_ptr right = boost::any_cast>(f.getRightSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(left, right)); + } + + } +} diff --git a/src/logic/CumulativeRewardFormula.h b/src/logic/CumulativeRewardFormula.h index 6a5e8c035..305c89895 100644 --- a/src/logic/CumulativeRewardFormula.h +++ b/src/logic/CumulativeRewardFormula.h @@ -33,7 +33,7 @@ namespace storm { double getContinuousTimeBound() const; virtual std::shared_ptr substitute(std::map const& substitution) const override; - + private: boost::variant timeBound; }; diff --git a/src/logic/EventuallyFormula.cpp b/src/logic/EventuallyFormula.cpp index 56c9decff..926c68af2 100644 --- a/src/logic/EventuallyFormula.cpp +++ b/src/logic/EventuallyFormula.cpp @@ -10,6 +10,10 @@ namespace storm { STORM_LOG_THROW(context == FormulaContext::Probability || context == FormulaContext::Reward || context == FormulaContext::Time, storm::exceptions::InvalidPropertyException, "Invalid context for formula."); } + FormulaContext const& EventuallyFormula::getContext() const { + return context; + } + bool EventuallyFormula::isEventuallyFormula() const { return true; } diff --git a/src/logic/EventuallyFormula.h b/src/logic/EventuallyFormula.h index f3716fee9..b21c92170 100644 --- a/src/logic/EventuallyFormula.h +++ b/src/logic/EventuallyFormula.h @@ -14,6 +14,8 @@ namespace storm { // Intentionally left empty. } + FormulaContext const& getContext() const; + virtual bool isEventuallyFormula() const override; virtual bool isReachabilityProbabilityFormula() const override; virtual bool isReachabilityRewardFormula() const override; diff --git a/src/logic/Formula.cpp b/src/logic/Formula.cpp index c48548f10..cc8a570be 100644 --- a/src/logic/Formula.cpp +++ b/src/logic/Formula.cpp @@ -3,6 +3,8 @@ #include "src/logic/FragmentChecker.h" #include "src/logic/FormulaInformationVisitor.h" +#include "src/logic/LabelSubstitutionVisitor.h" +#include "src/logic/ToExpressionVisitor.h" namespace storm { namespace logic { @@ -406,6 +408,16 @@ namespace storm { return referencedRewardModels; } + std::shared_ptr Formula::substitute(std::map const& labelSubstitution) const { + LabelSubstitutionVisitor visitor(labelSubstitution); + return visitor.substitute(*this); + } + + storm::expressions::Expression Formula::toExpression() const { + ToExpressionVisitor visitor; + return visitor.toExpression(*this); + } + std::shared_ptr Formula::asSharedPointer() { return this->shared_from_this(); } diff --git a/src/logic/Formula.h b/src/logic/Formula.h index b77ecfb20..3ac205abe 100644 --- a/src/logic/Formula.h +++ b/src/logic/Formula.h @@ -187,6 +187,15 @@ namespace storm { std::shared_ptr asSharedPointer() const; virtual std::shared_ptr substitute(std::map const& substitution) const = 0; + virtual std::shared_ptr substitute(std::map const& labelSubstitution) const; + + /*! + * Takes the formula and converts it to an equivalent expression assuming that only atomic expression formulas + * and boolean connectives appear in the formula. + * + * @return An equivalent expression. + */ + storm::expressions::Expression toExpression() const; std::string toString() const; virtual std::ostream& writeToStream(std::ostream& out) const = 0; diff --git a/src/logic/LabelSubstitutionVisitor.cpp b/src/logic/LabelSubstitutionVisitor.cpp new file mode 100644 index 000000000..ac05c09eb --- /dev/null +++ b/src/logic/LabelSubstitutionVisitor.cpp @@ -0,0 +1,27 @@ +#include "src/logic/LabelSubstitutionVisitor.h" + +#include "src/logic/Formulas.h" + +namespace storm { + namespace logic { + + LabelSubstitutionVisitor::LabelSubstitutionVisitor(std::map const& labelToExpressionMapping) : labelToExpressionMapping(labelToExpressionMapping) { + // Intentionally left empty. + } + + std::shared_ptr LabelSubstitutionVisitor::substitute(Formula const& f) const { + boost::any result = f.accept(*this, boost::any()); + return boost::any_cast>(result); + } + + boost::any LabelSubstitutionVisitor::visit(AtomicLabelFormula const& f, boost::any const& data) const { + auto it = labelToExpressionMapping.find(f.getLabel()); + if (it != labelToExpressionMapping.end()) { + return std::static_pointer_cast(std::make_shared(it->second)); + } else { + return std::static_pointer_cast(std::make_shared(f)); + } + } + + } +} diff --git a/src/logic/LabelSubstitutionVisitor.h b/src/logic/LabelSubstitutionVisitor.h new file mode 100644 index 000000000..dec06231e --- /dev/null +++ b/src/logic/LabelSubstitutionVisitor.h @@ -0,0 +1,29 @@ +#ifndef STORM_LOGIC_LABELSUBSTITUTIONVISITOR_H_ +#define STORM_LOGIC_LABELSUBSTITUTIONVISITOR_H_ + +#include + +#include "src/logic/CloneVisitor.h" + +#include "src/storage/expressions/Expression.h" + +namespace storm { + namespace logic { + + class LabelSubstitutionVisitor : public CloneVisitor { + public: + LabelSubstitutionVisitor(std::map const& labelToExpressionMapping); + + std::shared_ptr substitute(Formula const& f) const; + + virtual boost::any visit(AtomicLabelFormula const& f, boost::any const& data) const override; + + private: + std::map const& labelToExpressionMapping; + }; + + } +} + + +#endif /* STORM_LOGIC_FORMULAINFORMATIONVISITOR_H_ */ \ No newline at end of file diff --git a/src/logic/OperatorFormula.cpp b/src/logic/OperatorFormula.cpp index 4a8e2e215..474a9608b 100644 --- a/src/logic/OperatorFormula.cpp +++ b/src/logic/OperatorFormula.cpp @@ -50,6 +50,10 @@ namespace storm { return true; } + OperatorInformation const& OperatorFormula::getOperatorInformation() const { + return operatorInformation; + } + bool OperatorFormula::hasQualitativeResult() const { return this->hasBound(); } diff --git a/src/logic/OperatorFormula.h b/src/logic/OperatorFormula.h index 447647b99..ee7f7a87a 100644 --- a/src/logic/OperatorFormula.h +++ b/src/logic/OperatorFormula.h @@ -37,7 +37,9 @@ namespace storm { bool hasOptimalityType() const; storm::solver::OptimizationDirection const& getOptimalityType() const; virtual bool isOperatorFormula() const override; - + + OperatorInformation const& getOperatorInformation() const; + virtual bool hasQualitativeResult() const override; virtual bool hasQuantitativeResult() const override; diff --git a/src/logic/ToExpressionVisitor.cpp b/src/logic/ToExpressionVisitor.cpp new file mode 100644 index 000000000..887f9d405 --- /dev/null +++ b/src/logic/ToExpressionVisitor.cpp @@ -0,0 +1,103 @@ +#include "src/logic/ToExpressionVisitor.h" + +#include "src/logic/Formulas.h" + +#include "src/utility/macros.h" +#include "src/exceptions/InvalidOperationException.h" + +namespace storm { + namespace logic { + + storm::expressions::Expression ToExpressionVisitor::toExpression(Formula const& f) const { + boost::any result = f.accept(*this, boost::any()); + return boost::any_cast(result); + } + + boost::any ToExpressionVisitor::visit(AtomicExpressionFormula const& f, boost::any const& data) const { + return f.getExpression(); + } + + boost::any ToExpressionVisitor::visit(AtomicLabelFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(BinaryBooleanStateFormula const& f, boost::any const& data) const { + storm::expressions::Expression left = boost::any_cast(f.getLeftSubformula().accept(*this, data)); + storm::expressions::Expression right = boost::any_cast(f.getRightSubformula().accept(*this, data)); + switch (f.getOperator()) { + case BinaryBooleanStateFormula::OperatorType::And: + return left && right; + break; + case BinaryBooleanStateFormula::OperatorType::Or: + return left || right; + break; + } + } + + boost::any ToExpressionVisitor::visit(BooleanLiteralFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f)); + } + + boost::any ToExpressionVisitor::visit(BoundedUntilFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(ConditionalFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(CumulativeRewardFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(EventuallyFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(TimeOperatorFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(GloballyFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(InstantaneousRewardFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(LongRunAverageOperatorFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(LongRunAverageRewardFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(NextFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(ProbabilityOperatorFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(RewardOperatorFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + boost::any ToExpressionVisitor::visit(UnaryBooleanStateFormula const& f, boost::any const& data) const { + storm::expressions::Expression subexpression = boost::any_cast(f.getSubformula().accept(*this, data)); + switch (f.getOperator()) { + case UnaryBooleanStateFormula::OperatorType::Not: + return !subexpression; + break; + } + } + + boost::any ToExpressionVisitor::visit(UntilFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + } + + } +} diff --git a/src/logic/ToExpressionVisitor.h b/src/logic/ToExpressionVisitor.h new file mode 100644 index 000000000..a42258a46 --- /dev/null +++ b/src/logic/ToExpressionVisitor.h @@ -0,0 +1,39 @@ +#ifndef STORM_LOGIC_TOEXPRESSIONVISITOR_H_ +#define STORM_LOGIC_TOEXPRESSIONVISITOR_H_ + +#include "src/logic/FormulaVisitor.h" + +#include "src/storage/expressions/Expression.h" + +namespace storm { + namespace logic { + + class ToExpressionVisitor : public FormulaVisitor { + public: + storm::expressions::Expression toExpression(Formula const& f) const; + + virtual boost::any visit(AtomicExpressionFormula const& f, boost::any const& data) const override; + virtual boost::any visit(AtomicLabelFormula const& f, boost::any const& data) const override; + virtual boost::any visit(BinaryBooleanStateFormula const& f, boost::any const& data) const override; + virtual boost::any visit(BooleanLiteralFormula const& f, boost::any const& data) const override; + virtual boost::any visit(BoundedUntilFormula const& f, boost::any const& data) const override; + virtual boost::any visit(ConditionalFormula const& f, boost::any const& data) const override; + virtual boost::any visit(CumulativeRewardFormula const& f, boost::any const& data) const override; + virtual boost::any visit(EventuallyFormula const& f, boost::any const& data) const override; + virtual boost::any visit(TimeOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(GloballyFormula const& f, boost::any const& data) const override; + virtual boost::any visit(InstantaneousRewardFormula const& f, boost::any const& data) const override; + virtual boost::any visit(LongRunAverageOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(LongRunAverageRewardFormula const& f, boost::any const& data) const override; + virtual boost::any visit(NextFormula const& f, boost::any const& data) const override; + virtual boost::any visit(ProbabilityOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(RewardOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(UnaryBooleanStateFormula const& f, boost::any const& data) const override; + virtual boost::any visit(UntilFormula const& f, boost::any const& data) const override; + }; + + } +} + + +#endif /* STORM_LOGIC_TOEXPRESSIONVISITOR_H_ */ \ No newline at end of file diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index a762618f8..d2f1c47bf 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -38,7 +38,6 @@ namespace storm { storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "For nondeterministic systems, an optimization direction (min/max) must be given in the property."); - STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); StateGeneration stateGeneration(program, variableInformation, getTargetStateExpression(subformula)); @@ -58,13 +57,8 @@ namespace storm { template storm::expressions::Expression SparseMdpLearningModelChecker::getTargetStateExpression(storm::logic::Formula const& subformula) const { - storm::expressions::Expression result; - if (subformula.isAtomicExpressionFormula()) { - result = subformula.asAtomicExpressionFormula().getExpression(); - } else { - result = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); - } - return result; + std::shared_ptr preparedSubformula = subformula.substitute(program.getLabelToExpressionMapping()); + return preparedSubformula->toExpression(); } template @@ -101,7 +95,7 @@ namespace storm { Statistics stats; bool convergenceCriterionMet = false; while (!convergenceCriterionMet) { - bool result = samplePathFromState(stateGeneration, explorationInformation, stack, bounds, stats); + bool result = samplePathFromInitialState(stateGeneration, explorationInformation, stack, bounds, stats); // If a terminal state was found, we update the probabilities along the path contained in the stack. if (result) { @@ -125,7 +119,7 @@ namespace storm { if (storm::settings::generalSettings().isShowStatisticsSet()) { std::cout << std::endl << "Learning summary -------------------------" << std::endl; - std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target states)" << std::endl; + std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target)" << std::endl; std::cout << "Sampling iterations: " << stats.iterations << std::endl; std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; } @@ -134,11 +128,11 @@ namespace storm { } template - bool SparseMdpLearningModelChecker::samplePathFromState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const { - + bool SparseMdpLearningModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const { // Start the search from the initial state. stack.push_back(std::make_pair(explorationInformation.getFirstInitialState(), 0)); + // As long as we didn't find a terminal (accepting or rejecting) state in the search, sample a new successor. bool foundTerminalState = false; while (!foundTerminalState) { StateType const& currentStateId = stack.back().first; @@ -165,7 +159,7 @@ namespace storm { if (!foundTerminalState) { // At this point, we can be sure that the state was expanded and that we can sample according to the // probabilities in the matrix. - uint32_t chosenAction = sampleMaxAction(currentStateId, explorationInformation, bounds); + uint32_t chosenAction = sampleActionOfState(currentStateId, explorationInformation, bounds); stack.back().second = chosenAction; STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); @@ -242,7 +236,9 @@ namespace storm { explorationInformation.addRowsToMatrix(behavior.getNumberOfChoices()); ActionType currentAction = 0; - std::pair stateBounds(storm::utility::zero(), storm::utility::zero()); + + // Retrieve the lowest state bounds (wrt. to the current optimization direction). + std::pair stateBounds = getLowestBounds(explorationInformation.optimizationDirection); for (auto const& choice : behavior) { for (auto const& entry : choice) { @@ -251,7 +247,7 @@ namespace storm { std::pair actionBounds = computeBoundsOfAction(startRow + currentAction, explorationInformation, bounds); bounds.initializeBoundsForNextAction(actionBounds); - stateBounds = std::make_pair(std::max(stateBounds.first, actionBounds.first), std::max(stateBounds.second, actionBounds.second)); + stateBounds = combineBounds(explorationInformation.optimizationDirection, stateBounds, actionBounds); STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << bounds.getLowerBoundForAction(startRow + currentAction) << " and " << bounds.getUpperBoundForAction(startRow + currentAction) << "."); @@ -289,16 +285,12 @@ namespace storm { } template - uint32_t SparseMdpLearningModelChecker::sampleMaxAction(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { - StateType rowGroup = explorationInformation.getRowGroup(currentStateId); - - // First, determine all maximizing actions. - std::vector allMaxActions; - + uint32_t SparseMdpLearningModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { // Determine the values of all available actions. std::vector> actionValues; - auto choicesInEcIt = explorationInformation.stateToLeavingChoicesOfEndComponent.find(currentStateId); - if (choicesInEcIt != explorationInformation.stateToLeavingChoicesOfEndComponent.end()) { + StateType rowGroup = explorationInformation.getRowGroup(currentStateId); + auto choicesInEcIt = explorationInformation.stateToLeavingActionsOfEndComponent.find(currentStateId); + if (choicesInEcIt != explorationInformation.stateToLeavingActionsOfEndComponent.end()) { STORM_LOG_TRACE("Sampling from actions leaving the previously detected EC."); for (auto const& row : *choicesInEcIt->second) { actionValues.push_back(std::make_pair(row, computeUpperBoundOfAction(row, explorationInformation, bounds))); @@ -313,8 +305,14 @@ namespace storm { STORM_LOG_ASSERT(!actionValues.empty(), "Values for actions must not be empty."); - std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return a.second > b.second; } ); + // Sort the actions wrt. to the optimization direction. + if (explorationInformation.optimizationDirection == storm::OptimizationDirection::Maximize) { + std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return a.second > b.second; } ); + } else { + std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return a.second < b.second; } ); + } + // Determine the first elements of the sorted range that agree on their value. auto end = ++actionValues.begin(); while (end != actionValues.end() && comparator.isEqual(actionValues.begin()->second, end->second)) { ++end; @@ -416,14 +414,14 @@ namespace storm { StateType originalState = relevantStates[stateAndChoices.first]; uint32_t originalRowGroup = explorationInformation.getRowGroup(originalState); - // TODO: This checks for a target state is a bit hackish and only works for max probabilities. - if (!containsTargetState && comparator.isOne(bounds.getLowerBoundForRowGroup(originalRowGroup, explorationInformation))) { + // Check whether a target state is contained in the MEC. + if (!containsTargetState && comparator.isOne(bounds.getLowerBoundForRowGroup(originalRowGroup))) { containsTargetState = true; } - + + // For each state, compute the actions that leave the MEC. auto includedChoicesIt = stateAndChoices.second.begin(); auto includedChoicesIte = stateAndChoices.second.end(); - for (auto action = explorationInformation.getStartRowOfGroup(originalRowGroup); action < explorationInformation.getStartRowOfGroup(originalRowGroup + 1); ++action) { if (includedChoicesIt != includedChoicesIte) { STORM_LOG_TRACE("Next (local) choice contained in MEC is " << (*includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first])); @@ -441,7 +439,7 @@ namespace storm { } } - explorationInformation.stateToLeavingChoicesOfEndComponent[originalState] = leavingChoices; + explorationInformation.stateToLeavingActionsOfEndComponent[originalState] = leavingChoices; } // If one of the states of the EC is a target state, all states in the EC have probability 1. @@ -488,28 +486,6 @@ namespace storm { return result; } - template - ValueType SparseMdpLearningModelChecker::computeLowerBoundOfState(StateType const& state, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { - StateType group = explorationInformation.getRowGroup(state); - ValueType result = storm::utility::zero(); - for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { - ValueType actionValue = computeLowerBoundOfAction(action, explorationInformation, bounds); - result = std::max(actionValue, result); - } - return result; - } - - template - ValueType SparseMdpLearningModelChecker::computeUpperBoundOfState(StateType const& state, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { - StateType group = explorationInformation.getRowGroup(state); - ValueType result = storm::utility::zero(); - for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { - ValueType actionValue = computeUpperBoundOfAction(action, explorationInformation, bounds); - result = std::max(actionValue, result); - } - return result; - } - template std::pair SparseMdpLearningModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { // TODO: take into account self-loops? @@ -524,11 +500,10 @@ namespace storm { template std::pair SparseMdpLearningModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { StateType group = explorationInformation.getRowGroup(currentStateId); - std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); + std::pair result = getLowestBounds(explorationInformation.optimizationDirection); for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { std::pair actionValues = computeBoundsOfAction(action, explorationInformation, bounds); - result.first = std::max(actionValues.first, result.first); - result.second = std::max(actionValues.second, result.second); + result = combineBounds(explorationInformation.optimizationDirection, result, actionValues); } return result; } @@ -541,10 +516,44 @@ namespace storm { stack.pop_back(); } } + + template + void SparseMdpLearningModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + // Compute the new lower/upper values of the action. + std::pair newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds); + + // And set them as the current value. + bounds.setBoundsForAction(action, newBoundsForAction); + + // Check if we need to update the values for the states. + if (explorationInformation.optimizationDirection == storm::OptimizationDirection::Maximize) { + bounds.setLowerBoundOfStateIfGreaterThanOld(state, explorationInformation, newBoundsForAction.first); + + StateType rowGroup = explorationInformation.getRowGroup(state); + if (newBoundsForAction.second < bounds.getUpperBoundForRowGroup(rowGroup)) { + if (explorationInformation.getRowGroupSize(rowGroup) > 1) { + newBoundsForAction.second = std::max(newBoundsForAction.second, computeBoundOverAllOtherActions(storm::OptimizationDirection::Maximize, state, action, explorationInformation, bounds)); + } + + bounds.setUpperBoundForRowGroup(rowGroup, newBoundsForAction.second); + } + } else { + bounds.setUpperBoundOfStateIfLessThanOld(state, explorationInformation, newBoundsForAction.second); + StateType rowGroup = explorationInformation.getRowGroup(state); + if (bounds.getLowerBoundForRowGroup(rowGroup) < newBoundsForAction.first) { + if (explorationInformation.getRowGroupSize(rowGroup) > 1) { + newBoundsForAction.first = std::min(newBoundsForAction.first, computeBoundOverAllOtherActions(storm::OptimizationDirection::Maximize, state, action, explorationInformation, bounds)); + } + + bounds.setLowerBoundForRowGroup(rowGroup, newBoundsForAction.first); + } + } + } + template - ValueType SparseMdpLearningModelChecker::computeUpperBoundOverAllOtherActions(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { - ValueType max = storm::utility::zero(); + ValueType SparseMdpLearningModelChecker::computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType bound = getLowestBound(explorationInformation.optimizationDirection); ActionType group = explorationInformation.getRowGroup(state); for (auto currentAction = explorationInformation.getStartRowOfGroup(group); currentAction < explorationInformation.getStartRowOfGroup(group + 1); ++currentAction) { @@ -552,30 +561,36 @@ namespace storm { continue; } - max = std::max(max, computeUpperBoundOfAction(currentAction, explorationInformation, bounds)); + if (direction == storm::OptimizationDirection::Maximize) { + bound = std::max(bound, computeUpperBoundOfAction(currentAction, explorationInformation, bounds)); + } else { + bound = std::min(bound, computeLowerBoundOfAction(currentAction, explorationInformation, bounds)); + } } - - return max; + return bound; } template - void SparseMdpLearningModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { - // Compute the new lower/upper values of the action. - std::pair newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds); - - // And set them as the current value. - bounds.setBoundsForAction(action, newBoundsForAction); - - // Check if we need to update the values for the states. - bounds.setNewLowerBoundOfStateIfGreaterThanOld(state, explorationInformation, newBoundsForAction.first); - - StateType rowGroup = explorationInformation.getRowGroup(state); - if (newBoundsForAction.second < bounds.getUpperBoundForRowGroup(rowGroup)) { - if (explorationInformation.getRowGroupSize(rowGroup) > 1) { - newBoundsForAction.second = std::max(newBoundsForAction.second, computeUpperBoundOverAllOtherActions(state, action, explorationInformation, bounds)); - } - - bounds.setUpperBoundForState(state, explorationInformation, newBoundsForAction.second); + std::pair SparseMdpLearningModelChecker::getLowestBounds(storm::OptimizationDirection const& direction) const { + ValueType val = getLowestBound(direction); + return std::make_pair(val, val); + } + + template + ValueType SparseMdpLearningModelChecker::getLowestBound(storm::OptimizationDirection const& direction) const { + if (direction == storm::OptimizationDirection::Maximize) { + return storm::utility::zero(); + } else { + return storm::utility::one(); + } + } + + template + std::pair SparseMdpLearningModelChecker::combineBounds(storm::OptimizationDirection const& direction, std::pair const& bounds1, std::pair const& bounds2) const { + if (direction == storm::OptimizationDirection::Maximize) { + return std::make_pair(std::max(bounds1.first, bounds2.first), std::max(bounds1.second, bounds2.second)); + } else { + return std::make_pair(std::min(bounds1.first, bounds2.first), std::min(bounds1.second, bounds2.second)); } } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 01ea2d323..c3a931913 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -101,7 +101,7 @@ namespace storm { storm::OptimizationDirection optimizationDirection; StateSet terminalStates; - std::unordered_map stateToLeavingChoicesOfEndComponent; + std::unordered_map stateToLeavingActionsOfEndComponent; void setInitialStates(std::vector const& initialStates) { stateStorage.initialStateIndices = initialStates; @@ -208,11 +208,11 @@ namespace storm { if (index == explorationInformation.getUnexploredMarker()) { return storm::utility::zero(); } else { - return getLowerBoundForRowGroup(index, explorationInformation); + return getLowerBoundForRowGroup(index); } } - ValueType getLowerBoundForRowGroup(StateType const& rowGroup, ExplorationInformation const& explorationInformation) const { + ValueType const& getLowerBoundForRowGroup(StateType const& rowGroup) const { return lowerBoundsPerState[rowGroup]; } @@ -257,11 +257,19 @@ namespace storm { } void setLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value) { - lowerBoundsPerState[explorationInformation.getRowGroup(state)] = 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) { - upperBoundsPerState[explorationInformation.getRowGroup(state)] = value; + setUpperBoundForRowGroup(explorationInformation.getRowGroup(state), value); + } + + void setUpperBoundForRowGroup(StateType const& group, ValueType const& value) { + upperBoundsPerState[group] = value; } void setBoundsForAction(ActionType const& action, std::pair const& values) { @@ -275,7 +283,7 @@ namespace storm { upperBoundsPerState[rowGroup] = values.second; } - bool setNewLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newLowerValue) { + bool setLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newLowerValue) { StateType const& rowGroup = explorationInformation.getRowGroup(state); if (lowerBoundsPerState[rowGroup] < newLowerValue) { lowerBoundsPerState[rowGroup] = newLowerValue; @@ -284,7 +292,7 @@ namespace storm { return false; } - bool setNewUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newUpperValue) { + bool setUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newUpperValue) { StateType const& rowGroup = explorationInformation.getRowGroup(state); if (newUpperValue < upperBoundsPerState[rowGroup]) { upperBoundsPerState[rowGroup] = newUpperValue; @@ -300,11 +308,11 @@ namespace storm { std::tuple performLearningProcedure(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const; - bool samplePathFromState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const; + bool samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const; bool exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; - uint32_t sampleMaxAction(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; + uint32_t sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation) const; @@ -315,12 +323,14 @@ namespace storm { void updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; std::pair computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - ValueType computeUpperBoundOverAllOtherActions(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + ValueType computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; std::pair computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; ValueType computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; ValueType computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - ValueType computeLowerBoundOfState(StateType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - ValueType computeUpperBoundOfState(StateType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + + std::pair getLowestBounds(storm::OptimizationDirection const& direction) const; + ValueType getLowestBound(storm::OptimizationDirection const& direction) const; + std::pair combineBounds(storm::OptimizationDirection const& direction, std::pair const& bounds1, std::pair const& bounds2) const; // The program that defines the model to check. storm::prism::Program program; diff --git a/src/storage/prism/Program.cpp b/src/storage/prism/Program.cpp index 999cf2f30..b94f288c0 100644 --- a/src/storage/prism/Program.cpp +++ b/src/storage/prism/Program.cpp @@ -368,6 +368,14 @@ namespace storm { return this->labels[labelIndexPair->second].getStatePredicateExpression(); } + std::map Program::getLabelToExpressionMapping() const { + std::map result; + for (auto const& label : labels) { + result.emplace(label.getName(), label.getStatePredicateExpression()); + } + return result; + } + std::size_t Program::getNumberOfLabels() const { return this->getLabels().size(); } diff --git a/src/storage/prism/Program.h b/src/storage/prism/Program.h index 1d091c294..3e8245a72 100644 --- a/src/storage/prism/Program.h +++ b/src/storage/prism/Program.h @@ -379,6 +379,13 @@ namespace storm { */ storm::expressions::Expression const& getLabelExpression(std::string const& label) const; + /*! + * Retrieves a mapping from all labels in the program to their defining expressions. + * + * @return A mapping from label names to their expressions. + */ + std::map getLabelToExpressionMapping() const; + /*! * Retrieves the number of labels in the program. * From 8ea869ef14c1d0c964a33078b7cb82a3ac51218c Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 6 Apr 2016 21:56:25 +0200 Subject: [PATCH 18/31] changed detection of terminal states a bit Former-commit-id: a9229fa174420bb20cf0b50fed73850fc003cb0b --- .../SparseMdpLearningModelChecker.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index d2f1c47bf..76765d367 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -216,17 +216,16 @@ namespace storm { STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); // Clumsily check whether we have found a state that forms a trivial BMEC. - if (behavior.getNumberOfChoices() == 0) { - isTerminalState = true; - } else if (behavior.getNumberOfChoices() == 1) { - auto const& onlyChoice = *behavior.begin(); - if (onlyChoice.size() == 1) { - auto const& onlyEntry = *onlyChoice.begin(); - if (onlyEntry.first == currentStateId) { - isTerminalState = true; + bool otherSuccessor = false; + for (auto const& choice : behavior) { + for (auto const& entry : choice) { + if (entry.first != currentStateId) { + otherSuccessor = true; + break; } } } + isTerminalState = !otherSuccessor; // If the state was neither a trivial (non-accepting) terminal state nor a target state, we // need to store its behavior. From 599a3e99c729a189020e25f5715fc2056e63674c Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 7 Apr 2016 11:59:34 +0200 Subject: [PATCH 19/31] minimal probabilities now working (for some test cases) Former-commit-id: 9f38386531ba8428ead1fdd93bbede0f4e3fa93f --- src/logic/AtomicExpressionFormula.cpp | 4 - src/logic/AtomicExpressionFormula.h | 2 - src/logic/AtomicLabelFormula.cpp | 4 - src/logic/AtomicLabelFormula.h | 4 +- src/logic/BinaryBooleanStateFormula.cpp | 6 +- src/logic/BinaryBooleanStateFormula.h | 2 - src/logic/BooleanLiteralFormula.cpp | 4 - src/logic/BooleanLiteralFormula.h | 2 - src/logic/BoundedUntilFormula.cpp | 4 - src/logic/BoundedUntilFormula.h | 2 - src/logic/ConditionalFormula.cpp | 6 +- src/logic/ConditionalFormula.h | 2 - src/logic/CumulativeRewardFormula.cpp | 4 - src/logic/CumulativeRewardFormula.h | 2 - src/logic/EventuallyFormula.cpp | 6 +- src/logic/EventuallyFormula.h | 2 - src/logic/Formula.cpp | 6 + src/logic/Formula.h | 4 +- src/logic/GloballyFormula.cpp | 4 - src/logic/GloballyFormula.h | 2 - src/logic/InstantaneousRewardFormula.cpp | 6 +- src/logic/InstantaneousRewardFormula.h | 4 +- src/logic/LabelSubstitutionVisitor.cpp | 3 +- src/logic/LongRunAverageOperatorFormula.cpp | 6 +- src/logic/LongRunAverageOperatorFormula.h | 2 - src/logic/LongRunAverageRewardFormula.cpp | 4 - src/logic/LongRunAverageRewardFormula.h | 2 - src/logic/NextFormula.cpp | 6 +- src/logic/NextFormula.h | 4 +- src/logic/ProbabilityOperatorFormula.cpp | 6 +- src/logic/ProbabilityOperatorFormula.h | 4 +- src/logic/RewardOperatorFormula.cpp | 4 - src/logic/RewardOperatorFormula.h | 4 +- src/logic/TimeOperatorFormula.cpp | 6 +- src/logic/TimeOperatorFormula.h | 4 +- src/logic/UnaryBooleanStateFormula.cpp | 6 +- src/logic/UnaryBooleanStateFormula.h | 2 - src/logic/UntilFormula.cpp | 4 - src/logic/UntilFormula.h | 4 +- src/logic/VariableSubstitutionVisitor.cpp | 21 ++ src/logic/VariableSubstitutionVisitor.h | 29 +++ .../SparseMdpLearningModelChecker.cpp | 186 +++++++++++------- .../SparseMdpLearningModelChecker.h | 30 ++- 43 files changed, 220 insertions(+), 199 deletions(-) create mode 100644 src/logic/VariableSubstitutionVisitor.cpp create mode 100644 src/logic/VariableSubstitutionVisitor.h diff --git a/src/logic/AtomicExpressionFormula.cpp b/src/logic/AtomicExpressionFormula.cpp index 5f8f0f509..b782fd368 100644 --- a/src/logic/AtomicExpressionFormula.cpp +++ b/src/logic/AtomicExpressionFormula.cpp @@ -24,10 +24,6 @@ namespace storm { atomicExpressionFormulas.push_back(std::dynamic_pointer_cast(this->shared_from_this())); } - std::shared_ptr AtomicExpressionFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->expression.substitute(substitution)); - } - std::ostream& AtomicExpressionFormula::writeToStream(std::ostream& out) const { out << expression; return out; diff --git a/src/logic/AtomicExpressionFormula.h b/src/logic/AtomicExpressionFormula.h index 12636475d..2299b5eac 100644 --- a/src/logic/AtomicExpressionFormula.h +++ b/src/logic/AtomicExpressionFormula.h @@ -23,8 +23,6 @@ namespace storm { virtual void gatherAtomicExpressionFormulas(std::vector>& atomicExpressionFormulas) const override; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - private: // The atomic expression represented by this node in the formula tree. storm::expressions::Expression expression; diff --git a/src/logic/AtomicLabelFormula.cpp b/src/logic/AtomicLabelFormula.cpp index 292351c5d..b0406d62c 100644 --- a/src/logic/AtomicLabelFormula.cpp +++ b/src/logic/AtomicLabelFormula.cpp @@ -25,10 +25,6 @@ namespace storm { atomicExpressionFormulas.push_back(std::dynamic_pointer_cast(this->shared_from_this())); } - std::shared_ptr AtomicLabelFormula::substitute(std::map const& substitution) const { - return std::make_shared(*this); - } - std::ostream& AtomicLabelFormula::writeToStream(std::ostream& out) const { out << "\"" << label << "\""; return out; diff --git a/src/logic/AtomicLabelFormula.h b/src/logic/AtomicLabelFormula.h index 795704305..a7a627f74 100644 --- a/src/logic/AtomicLabelFormula.h +++ b/src/logic/AtomicLabelFormula.h @@ -22,9 +22,7 @@ namespace storm { std::string const& getLabel() const; virtual void gatherAtomicLabelFormulas(std::vector>& atomicLabelFormulas) const override; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; - + virtual std::ostream& writeToStream(std::ostream& out) const override; private: diff --git a/src/logic/BinaryBooleanStateFormula.cpp b/src/logic/BinaryBooleanStateFormula.cpp index d6757e508..a697b151c 100644 --- a/src/logic/BinaryBooleanStateFormula.cpp +++ b/src/logic/BinaryBooleanStateFormula.cpp @@ -30,11 +30,7 @@ namespace storm { bool BinaryBooleanStateFormula::isOr() const { return this->getOperator() == OperatorType::Or; } - - std::shared_ptr BinaryBooleanStateFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->operatorType, this->getLeftSubformula().substitute(substitution), this->getRightSubformula().substitute(substitution)); - } - + std::ostream& BinaryBooleanStateFormula::writeToStream(std::ostream& out) const { out << "("; this->getLeftSubformula().writeToStream(out); diff --git a/src/logic/BinaryBooleanStateFormula.h b/src/logic/BinaryBooleanStateFormula.h index c0880d058..d94260caa 100644 --- a/src/logic/BinaryBooleanStateFormula.h +++ b/src/logic/BinaryBooleanStateFormula.h @@ -28,8 +28,6 @@ namespace storm { virtual std::ostream& writeToStream(std::ostream& out) const override; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - private: OperatorType operatorType; }; diff --git a/src/logic/BooleanLiteralFormula.cpp b/src/logic/BooleanLiteralFormula.cpp index 897fbf377..2cc139cf3 100644 --- a/src/logic/BooleanLiteralFormula.cpp +++ b/src/logic/BooleanLiteralFormula.cpp @@ -24,10 +24,6 @@ namespace storm { return visitor.visit(*this, data); } - std::shared_ptr BooleanLiteralFormula::substitute(std::map const& substitution) const { - return std::make_shared(*this); - } - std::ostream& BooleanLiteralFormula::writeToStream(std::ostream& out) const { if (value) { out << "true"; diff --git a/src/logic/BooleanLiteralFormula.h b/src/logic/BooleanLiteralFormula.h index 955d1dbbe..0946852fc 100644 --- a/src/logic/BooleanLiteralFormula.h +++ b/src/logic/BooleanLiteralFormula.h @@ -19,8 +19,6 @@ namespace storm { virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - virtual std::ostream& writeToStream(std::ostream& out) const override; private: diff --git a/src/logic/BoundedUntilFormula.cpp b/src/logic/BoundedUntilFormula.cpp index 8f3e73286..dd9f965f7 100644 --- a/src/logic/BoundedUntilFormula.cpp +++ b/src/logic/BoundedUntilFormula.cpp @@ -44,10 +44,6 @@ namespace storm { return boost::get(bounds); } - std::shared_ptr BoundedUntilFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getLeftSubformula().substitute(substitution), this->getRightSubformula().substitute(substitution), bounds); - } - std::ostream& BoundedUntilFormula::writeToStream(std::ostream& out) const { this->getLeftSubformula().writeToStream(out); diff --git a/src/logic/BoundedUntilFormula.h b/src/logic/BoundedUntilFormula.h index 0e18633ce..13ee7cefc 100644 --- a/src/logic/BoundedUntilFormula.h +++ b/src/logic/BoundedUntilFormula.h @@ -26,8 +26,6 @@ namespace storm { virtual std::ostream& writeToStream(std::ostream& out) const override; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - private: boost::variant> bounds; }; diff --git a/src/logic/ConditionalFormula.cpp b/src/logic/ConditionalFormula.cpp index 55bddabd9..fd1b1b442 100644 --- a/src/logic/ConditionalFormula.cpp +++ b/src/logic/ConditionalFormula.cpp @@ -33,11 +33,7 @@ namespace storm { boost::any ConditionalFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } - - std::shared_ptr ConditionalFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution), this->getConditionFormula().substitute(substitution), context); - } - + void ConditionalFormula::gatherAtomicExpressionFormulas(std::vector>& atomicExpressionFormulas) const { this->getSubformula().gatherAtomicExpressionFormulas(atomicExpressionFormulas); this->getConditionFormula().gatherAtomicExpressionFormulas(atomicExpressionFormulas); diff --git a/src/logic/ConditionalFormula.h b/src/logic/ConditionalFormula.h index 87303198c..6bb4317b6 100644 --- a/src/logic/ConditionalFormula.h +++ b/src/logic/ConditionalFormula.h @@ -25,8 +25,6 @@ namespace storm { virtual std::ostream& writeToStream(std::ostream& out) const override; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - virtual void gatherAtomicExpressionFormulas(std::vector>& atomicExpressionFormulas) const override; virtual void gatherAtomicLabelFormulas(std::vector>& atomicLabelFormulas) const override; virtual void gatherReferencedRewardModels(std::set& referencedRewardModels) const override; diff --git a/src/logic/CumulativeRewardFormula.cpp b/src/logic/CumulativeRewardFormula.cpp index d8318164d..f32d476e5 100644 --- a/src/logic/CumulativeRewardFormula.cpp +++ b/src/logic/CumulativeRewardFormula.cpp @@ -44,10 +44,6 @@ namespace storm { } } - std::shared_ptr CumulativeRewardFormula::substitute(std::map const& substitution) const { - return std::make_shared(*this); - } - std::ostream& CumulativeRewardFormula::writeToStream(std::ostream& out) const { if (this->hasDiscreteTimeBound()) { out << "C<=" << this->getDiscreteTimeBound(); diff --git a/src/logic/CumulativeRewardFormula.h b/src/logic/CumulativeRewardFormula.h index 305c89895..422bc09ff 100644 --- a/src/logic/CumulativeRewardFormula.h +++ b/src/logic/CumulativeRewardFormula.h @@ -32,8 +32,6 @@ namespace storm { double getContinuousTimeBound() const; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - private: boost::variant timeBound; }; diff --git a/src/logic/EventuallyFormula.cpp b/src/logic/EventuallyFormula.cpp index 926c68af2..e83609e1b 100644 --- a/src/logic/EventuallyFormula.cpp +++ b/src/logic/EventuallyFormula.cpp @@ -45,11 +45,7 @@ namespace storm { boost::any EventuallyFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } - - std::shared_ptr EventuallyFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution), context); - } - + std::ostream& EventuallyFormula::writeToStream(std::ostream& out) const { out << "F "; this->getSubformula().writeToStream(out); diff --git a/src/logic/EventuallyFormula.h b/src/logic/EventuallyFormula.h index b21c92170..779b36f81 100644 --- a/src/logic/EventuallyFormula.h +++ b/src/logic/EventuallyFormula.h @@ -28,8 +28,6 @@ namespace storm { virtual std::ostream& writeToStream(std::ostream& out) const override; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - private: FormulaContext context; }; diff --git a/src/logic/Formula.cpp b/src/logic/Formula.cpp index cc8a570be..492230a33 100644 --- a/src/logic/Formula.cpp +++ b/src/logic/Formula.cpp @@ -3,6 +3,7 @@ #include "src/logic/FragmentChecker.h" #include "src/logic/FormulaInformationVisitor.h" +#include "src/logic/VariableSubstitutionVisitor.h" #include "src/logic/LabelSubstitutionVisitor.h" #include "src/logic/ToExpressionVisitor.h" @@ -408,6 +409,11 @@ namespace storm { return referencedRewardModels; } + std::shared_ptr Formula::substitute(std::map const& substitution) const { + VariableSubstitutionVisitor visitor(substitution); + return visitor.substitute(*this); + } + std::shared_ptr Formula::substitute(std::map const& labelSubstitution) const { LabelSubstitutionVisitor visitor(labelSubstitution); return visitor.substitute(*this); diff --git a/src/logic/Formula.h b/src/logic/Formula.h index 3ac205abe..11865b950 100644 --- a/src/logic/Formula.h +++ b/src/logic/Formula.h @@ -186,8 +186,8 @@ namespace storm { std::shared_ptr asSharedPointer(); std::shared_ptr asSharedPointer() const; - virtual std::shared_ptr substitute(std::map const& substitution) const = 0; - virtual std::shared_ptr substitute(std::map const& labelSubstitution) const; + std::shared_ptr substitute(std::map const& substitution) const; + std::shared_ptr substitute(std::map const& labelSubstitution) const; /*! * Takes the formula and converts it to an equivalent expression assuming that only atomic expression formulas diff --git a/src/logic/GloballyFormula.cpp b/src/logic/GloballyFormula.cpp index df33f0b52..ae6f5b508 100644 --- a/src/logic/GloballyFormula.cpp +++ b/src/logic/GloballyFormula.cpp @@ -19,10 +19,6 @@ namespace storm { boost::any GloballyFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } - - std::shared_ptr GloballyFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution)); - } std::ostream& GloballyFormula::writeToStream(std::ostream& out) const { out << "G "; diff --git a/src/logic/GloballyFormula.h b/src/logic/GloballyFormula.h index 011c61de9..e17347e11 100644 --- a/src/logic/GloballyFormula.h +++ b/src/logic/GloballyFormula.h @@ -17,8 +17,6 @@ namespace storm { virtual bool isProbabilityPathFormula() const override; virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; virtual std::ostream& writeToStream(std::ostream& out) const override; }; diff --git a/src/logic/InstantaneousRewardFormula.cpp b/src/logic/InstantaneousRewardFormula.cpp index 4f1f65429..bf39cd763 100644 --- a/src/logic/InstantaneousRewardFormula.cpp +++ b/src/logic/InstantaneousRewardFormula.cpp @@ -43,11 +43,7 @@ namespace storm { return boost::get(timeBound); } } - - std::shared_ptr InstantaneousRewardFormula::substitute(std::map const& substitution) const { - return std::make_shared(*this); - } - + std::ostream& InstantaneousRewardFormula::writeToStream(std::ostream& out) const { if (this->hasDiscreteTimeBound()) { out << "I=" << this->getDiscreteTimeBound(); diff --git a/src/logic/InstantaneousRewardFormula.h b/src/logic/InstantaneousRewardFormula.h index 069bf21bd..85d27c450 100644 --- a/src/logic/InstantaneousRewardFormula.h +++ b/src/logic/InstantaneousRewardFormula.h @@ -32,9 +32,7 @@ namespace storm { bool hasContinuousTimeBound() const; double getContinuousTimeBound() const; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; - + private: boost::variant timeBound; }; diff --git a/src/logic/LabelSubstitutionVisitor.cpp b/src/logic/LabelSubstitutionVisitor.cpp index ac05c09eb..ffb28dbed 100644 --- a/src/logic/LabelSubstitutionVisitor.cpp +++ b/src/logic/LabelSubstitutionVisitor.cpp @@ -21,7 +21,6 @@ namespace storm { } else { return std::static_pointer_cast(std::make_shared(f)); } - } - + } } } diff --git a/src/logic/LongRunAverageOperatorFormula.cpp b/src/logic/LongRunAverageOperatorFormula.cpp index f2e253ead..bf6b250ea 100644 --- a/src/logic/LongRunAverageOperatorFormula.cpp +++ b/src/logic/LongRunAverageOperatorFormula.cpp @@ -18,11 +18,7 @@ namespace storm { boost::any LongRunAverageOperatorFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } - - std::shared_ptr LongRunAverageOperatorFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution), this->operatorInformation); - } - + std::ostream& LongRunAverageOperatorFormula::writeToStream(std::ostream& out) const { out << "LRA"; OperatorFormula::writeToStream(out); diff --git a/src/logic/LongRunAverageOperatorFormula.h b/src/logic/LongRunAverageOperatorFormula.h index 394188b67..3590ac5b2 100644 --- a/src/logic/LongRunAverageOperatorFormula.h +++ b/src/logic/LongRunAverageOperatorFormula.h @@ -17,8 +17,6 @@ namespace storm { virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - virtual std::shared_ptr substitute(std::map const& substitution) const override; - virtual std::ostream& writeToStream(std::ostream& out) const override; }; } diff --git a/src/logic/LongRunAverageRewardFormula.cpp b/src/logic/LongRunAverageRewardFormula.cpp index 30bfd7ba9..3207b9bf2 100644 --- a/src/logic/LongRunAverageRewardFormula.cpp +++ b/src/logic/LongRunAverageRewardFormula.cpp @@ -20,10 +20,6 @@ namespace storm { return visitor.visit(*this, data); } - std::shared_ptr LongRunAverageRewardFormula::substitute(std::map const& substitution) const { - return std::shared_ptr(new LongRunAverageRewardFormula()); - } - std::ostream& LongRunAverageRewardFormula::writeToStream(std::ostream& out) const { return out << "LRA"; } diff --git a/src/logic/LongRunAverageRewardFormula.h b/src/logic/LongRunAverageRewardFormula.h index 3dfea465e..d65e55314 100644 --- a/src/logic/LongRunAverageRewardFormula.h +++ b/src/logic/LongRunAverageRewardFormula.h @@ -17,8 +17,6 @@ namespace storm { virtual bool isRewardPathFormula() const override; virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; virtual std::ostream& writeToStream(std::ostream& out) const override; diff --git a/src/logic/NextFormula.cpp b/src/logic/NextFormula.cpp index e73171a95..a0916e14f 100644 --- a/src/logic/NextFormula.cpp +++ b/src/logic/NextFormula.cpp @@ -19,11 +19,7 @@ namespace storm { boost::any NextFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } - - std::shared_ptr NextFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution)); - } - + std::ostream& NextFormula::writeToStream(std::ostream& out) const { out << "X "; this->getSubformula().writeToStream(out); diff --git a/src/logic/NextFormula.h b/src/logic/NextFormula.h index bade60456..4d895a48e 100644 --- a/src/logic/NextFormula.h +++ b/src/logic/NextFormula.h @@ -17,9 +17,7 @@ namespace storm { virtual bool isProbabilityPathFormula() const override; virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; - + virtual std::ostream& writeToStream(std::ostream& out) const override; }; } diff --git a/src/logic/ProbabilityOperatorFormula.cpp b/src/logic/ProbabilityOperatorFormula.cpp index 84d6c55d8..e630e45da 100644 --- a/src/logic/ProbabilityOperatorFormula.cpp +++ b/src/logic/ProbabilityOperatorFormula.cpp @@ -18,11 +18,7 @@ namespace storm { boost::any ProbabilityOperatorFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } - - std::shared_ptr ProbabilityOperatorFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution), this->operatorInformation); - } - + std::ostream& ProbabilityOperatorFormula::writeToStream(std::ostream& out) const { out << "P"; OperatorFormula::writeToStream(out); diff --git a/src/logic/ProbabilityOperatorFormula.h b/src/logic/ProbabilityOperatorFormula.h index 786d58b44..b1259262c 100644 --- a/src/logic/ProbabilityOperatorFormula.h +++ b/src/logic/ProbabilityOperatorFormula.h @@ -16,9 +16,7 @@ namespace storm { virtual bool isProbabilityOperatorFormula() const override; virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; - + virtual std::ostream& writeToStream(std::ostream& out) const override; }; } diff --git a/src/logic/RewardOperatorFormula.cpp b/src/logic/RewardOperatorFormula.cpp index 5daea4b59..aba2ec92a 100644 --- a/src/logic/RewardOperatorFormula.cpp +++ b/src/logic/RewardOperatorFormula.cpp @@ -40,10 +40,6 @@ namespace storm { this->getSubformula().gatherReferencedRewardModels(referencedRewardModels); } - std::shared_ptr RewardOperatorFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution), this->rewardModelName, this->operatorInformation, this->rewardMeasureType); - } - RewardMeasureType RewardOperatorFormula::getMeasureType() const { return rewardMeasureType; } diff --git a/src/logic/RewardOperatorFormula.h b/src/logic/RewardOperatorFormula.h index 300a82e37..99ca17233 100644 --- a/src/logic/RewardOperatorFormula.h +++ b/src/logic/RewardOperatorFormula.h @@ -50,9 +50,7 @@ namespace storm { * @return The measure type. */ RewardMeasureType getMeasureType() const; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; - + private: // The (optional) name of the reward model this property refers to. boost::optional rewardModelName; diff --git a/src/logic/TimeOperatorFormula.cpp b/src/logic/TimeOperatorFormula.cpp index 6a421a1a5..e7f711729 100644 --- a/src/logic/TimeOperatorFormula.cpp +++ b/src/logic/TimeOperatorFormula.cpp @@ -18,11 +18,7 @@ namespace storm { boost::any TimeOperatorFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } - - std::shared_ptr TimeOperatorFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getSubformula().substitute(substitution), this->operatorInformation, this->rewardMeasureType); - } - + RewardMeasureType TimeOperatorFormula::getMeasureType() const { return rewardMeasureType; } diff --git a/src/logic/TimeOperatorFormula.h b/src/logic/TimeOperatorFormula.h index b9f243a4c..24906bb6e 100644 --- a/src/logic/TimeOperatorFormula.h +++ b/src/logic/TimeOperatorFormula.h @@ -18,9 +18,7 @@ namespace storm { virtual bool isTimeOperatorFormula() const override; virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; - + virtual std::ostream& writeToStream(std::ostream& out) const override; /*! diff --git a/src/logic/UnaryBooleanStateFormula.cpp b/src/logic/UnaryBooleanStateFormula.cpp index 3983e5b27..83feb7fd4 100644 --- a/src/logic/UnaryBooleanStateFormula.cpp +++ b/src/logic/UnaryBooleanStateFormula.cpp @@ -26,11 +26,7 @@ namespace storm { bool UnaryBooleanStateFormula::isNot() const { return this->getOperator() == OperatorType::Not; } - - std::shared_ptr UnaryBooleanStateFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->operatorType, this->getSubformula().substitute(substitution)); - } - + std::ostream& UnaryBooleanStateFormula::writeToStream(std::ostream& out) const { switch (operatorType) { case OperatorType::Not: out << "!("; break; diff --git a/src/logic/UnaryBooleanStateFormula.h b/src/logic/UnaryBooleanStateFormula.h index 93d45f862..a30886a60 100644 --- a/src/logic/UnaryBooleanStateFormula.h +++ b/src/logic/UnaryBooleanStateFormula.h @@ -22,8 +22,6 @@ namespace storm { OperatorType getOperator() const; virtual bool isNot() const; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; virtual std::ostream& writeToStream(std::ostream& out) const override; diff --git a/src/logic/UntilFormula.cpp b/src/logic/UntilFormula.cpp index a00eae77d..3aaf93da1 100644 --- a/src/logic/UntilFormula.cpp +++ b/src/logic/UntilFormula.cpp @@ -20,10 +20,6 @@ namespace storm { return visitor.visit(*this, data); } - std::shared_ptr UntilFormula::substitute(std::map const& substitution) const { - return std::make_shared(this->getLeftSubformula().substitute(substitution), this->getRightSubformula().substitute(substitution)); - } - std::ostream& UntilFormula::writeToStream(std::ostream& out) const { this->getLeftSubformula().writeToStream(out); out << " U "; diff --git a/src/logic/UntilFormula.h b/src/logic/UntilFormula.h index 887d39b45..e03d32047 100644 --- a/src/logic/UntilFormula.h +++ b/src/logic/UntilFormula.h @@ -17,9 +17,7 @@ namespace storm { virtual bool isProbabilityPathFormula() const override; virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; - - virtual std::shared_ptr substitute(std::map const& substitution) const override; - + virtual std::ostream& writeToStream(std::ostream& out) const override; }; } diff --git a/src/logic/VariableSubstitutionVisitor.cpp b/src/logic/VariableSubstitutionVisitor.cpp new file mode 100644 index 000000000..9a5ba5b8a --- /dev/null +++ b/src/logic/VariableSubstitutionVisitor.cpp @@ -0,0 +1,21 @@ +#include "src/logic/VariableSubstitutionVisitor.h" + +#include "src/logic/Formulas.h" + +namespace storm { + namespace logic { + + VariableSubstitutionVisitor::VariableSubstitutionVisitor(std::map const& substitution) : substitution(substitution) { + // Intentionally left empty. + } + + std::shared_ptr VariableSubstitutionVisitor::substitute(Formula const& f) const { + boost::any result = f.accept(*this, boost::any()); + return boost::any_cast>(result); + } + + boost::any VariableSubstitutionVisitor::visit(AtomicExpressionFormula const& f, boost::any const& data) const { + return std::static_pointer_cast(std::make_shared(f.getExpression().substitute(substitution))); + } + } +} diff --git a/src/logic/VariableSubstitutionVisitor.h b/src/logic/VariableSubstitutionVisitor.h new file mode 100644 index 000000000..11876ba59 --- /dev/null +++ b/src/logic/VariableSubstitutionVisitor.h @@ -0,0 +1,29 @@ +#ifndef STORM_LOGIC_VARIABLESUBSTITUTIONVISITOR_H_ +#define STORM_LOGIC_VARIABLESUBSTITUTIONVISITOR_H_ + +#include + +#include "src/logic/CloneVisitor.h" + +#include "src/storage/expressions/Expression.h" + +namespace storm { + namespace logic { + + class VariableSubstitutionVisitor : public CloneVisitor { + public: + VariableSubstitutionVisitor(std::map const& substitution); + + std::shared_ptr substitute(Formula const& f) const; + + virtual boost::any visit(AtomicExpressionFormula const& f, boost::any const& data) const override; + + private: + std::map const& substitution; + }; + + } +} + + +#endif /* STORM_LOGIC_VARIABLESUBSTITUTIONVISITOR_H_ */ \ No newline at end of file diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 76765d367..f8adee32e 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -15,6 +15,7 @@ #include "src/settings/modules/GeneralSettings.h" #include "src/utility/macros.h" +#include "src/exceptions/InvalidOperationException.h" #include "src/exceptions/InvalidPropertyException.h" #include "src/exceptions/NotSupportedException.h" @@ -58,7 +59,13 @@ namespace storm { template storm::expressions::Expression SparseMdpLearningModelChecker::getTargetStateExpression(storm::logic::Formula const& subformula) const { std::shared_ptr preparedSubformula = subformula.substitute(program.getLabelToExpressionMapping()); - return preparedSubformula->toExpression(); + storm::expressions::Expression result; + try { + result = preparedSubformula->toExpression(); + } catch(storm::exceptions::InvalidOperationException const& e) { + STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "The property refers to unknown labels."); + } + return result; } template @@ -284,28 +291,41 @@ namespace storm { } template - uint32_t SparseMdpLearningModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + typename SparseMdpLearningModelChecker::ActionType SparseMdpLearningModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { // Determine the values of all available actions. std::vector> actionValues; StateType rowGroup = explorationInformation.getRowGroup(currentStateId); auto choicesInEcIt = explorationInformation.stateToLeavingActionsOfEndComponent.find(currentStateId); + + // Check for cases in which we do not need to perform more work. + if (choicesInEcIt == explorationInformation.stateToLeavingActionsOfEndComponent.end()) { + if (explorationInformation.onlyOneActionAvailable(rowGroup)) { + return explorationInformation.getStartRowOfGroup(rowGroup); + } + } else { + if (choicesInEcIt->second->size() == 1) { + return *choicesInEcIt->second->begin(); + } + } + + // If there are more choices to consider, start by gathering the values of relevant actions. if (choicesInEcIt != explorationInformation.stateToLeavingActionsOfEndComponent.end()) { STORM_LOG_TRACE("Sampling from actions leaving the previously detected EC."); for (auto const& row : *choicesInEcIt->second) { - actionValues.push_back(std::make_pair(row, computeUpperBoundOfAction(row, explorationInformation, bounds))); + actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.optimizationDirection, row))); } } else { STORM_LOG_TRACE("Sampling from actions leaving the state."); for (uint32_t row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) { - actionValues.push_back(std::make_pair(row, computeUpperBoundOfAction(row, explorationInformation, bounds))); + actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.optimizationDirection, row))); } } STORM_LOG_ASSERT(!actionValues.empty(), "Values for actions must not be empty."); // Sort the actions wrt. to the optimization direction. - if (explorationInformation.optimizationDirection == storm::OptimizationDirection::Maximize) { + if (explorationInformation.maximize()) { std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return a.second > b.second; } ); } else { std::sort(actionValues.begin(), actionValues.end(), [] (std::pair const& a, std::pair const& b) { return a.second < b.second; } ); @@ -349,14 +369,13 @@ namespace storm { // Determine the set of states that was expanded. std::vector relevantStates; for (StateType state = 0; state < explorationInformation.stateStorage.numberOfStates; ++state) { - if (!explorationInformation.isUnexplored(state)) { + // Add the state to the relevant states if it's unexplored. Additionally, if we are computing minimal + // probabilities, we only consider it relevant if it's not a target state. + if (!explorationInformation.isUnexplored(state) && (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(state, explorationInformation)))) { relevantStates.push_back(state); } } - - // Sort according to the actual row groups so we can insert the elements in order later. - std::sort(relevantStates.begin(), relevantStates.end(), [&explorationInformation] (StateType const& a, StateType const& b) { return explorationInformation.getRowGroup(a) < explorationInformation.getRowGroup(b); }); - StateType unexploredState = relevantStates.size(); + StateType sink = relevantStates.size(); // Create a mapping for faster look-up during the translation of flexible matrix to the real sparse matrix. std::unordered_map relevantStateToNewRowGroupMapping; @@ -382,14 +401,14 @@ namespace storm { } } if (unexpandedProbability != storm::utility::zero()) { - builder.addNextValue(currentRow, unexploredState, unexpandedProbability); + builder.addNextValue(currentRow, sink, unexpandedProbability); } ++currentRow; } } // Then, make the unexpanded state absorbing. builder.newRowGroup(currentRow); - builder.addNextValue(currentRow, unexploredState, storm::utility::one()); + builder.addNextValue(currentRow, sink, storm::utility::one()); STORM_LOG_TRACE("Successfully built matrix for MEC decomposition."); // Go on to step 2. @@ -399,74 +418,102 @@ namespace storm { // 3. Analyze the MEC decomposition. for (auto const& mec : mecDecomposition) { - // Ignore the (expected) MEC of the unexplored state. - if (mec.containsState(unexploredState)) { + // Ignore the (expected) MEC of the sink state. + if (mec.containsState(sink)) { continue; } - bool containsTargetState = false; + if (explorationInformation.maximize()) { + analyzeMecForMaximalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); + } else { + analyzeMecForMinimalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); + } + } + } + + template + void SparseMdpLearningModelChecker::analyzeMecForMaximalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { + // For maximal probabilities, we check (a) which MECs contain a target state, because the associated states + // have a probability of 1 (and we can therefore set their lower bounds to 1) and (b) which of the remaining + // MECs have no outgoing action, because the associated states have a probability of 0 (and we can therefore + // set their upper bounds to 0). + + bool containsTargetState = false; + + // Now we record all choices leaving the EC. + ActionSetPointer leavingChoices = std::make_shared(); + for (auto const& stateAndChoices : mec) { + // Compute the state of the original model that corresponds to the current state. + StateType originalState = relevantStates[stateAndChoices.first]; + StateType originalRowGroup = explorationInformation.getRowGroup(originalState); - // Now we record all choices leaving the EC. - ActionSetPointer leavingChoices = std::make_shared(); - for (auto const& stateAndChoices : mec) { - // Compute the state of the original model that corresponds to the current state. - StateType originalState = relevantStates[stateAndChoices.first]; - uint32_t originalRowGroup = explorationInformation.getRowGroup(originalState); - - // Check whether a target state is contained in the MEC. - if (!containsTargetState && comparator.isOne(bounds.getLowerBoundForRowGroup(originalRowGroup))) { - containsTargetState = true; - } - - // For each state, compute the actions that leave the MEC. - auto includedChoicesIt = stateAndChoices.second.begin(); - auto includedChoicesIte = stateAndChoices.second.end(); - for (auto action = explorationInformation.getStartRowOfGroup(originalRowGroup); action < explorationInformation.getStartRowOfGroup(originalRowGroup + 1); ++action) { - if (includedChoicesIt != includedChoicesIte) { - STORM_LOG_TRACE("Next (local) choice contained in MEC is " << (*includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first])); - STORM_LOG_TRACE("Current (local) choice iterated is " << (action - explorationInformation.getStartRowOfGroup(originalRowGroup))); - if (action - explorationInformation.getStartRowOfGroup(originalRowGroup) != *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { - STORM_LOG_TRACE("Choice leaves the EC."); - leavingChoices->insert(action); - } else { - STORM_LOG_TRACE("Choice stays in the EC."); - ++includedChoicesIt; - } - } else { - STORM_LOG_TRACE("Choice leaves the EC, because there is no more choice staying in the EC."); + // Check whether a target state is contained in the MEC. + if (!containsTargetState && comparator.isOne(bounds.getLowerBoundForRowGroup(originalRowGroup))) { + containsTargetState = true; + } + + // For each state, compute the actions that leave the MEC. + auto includedChoicesIt = stateAndChoices.second.begin(); + auto includedChoicesIte = stateAndChoices.second.end(); + for (auto action = explorationInformation.getStartRowOfGroup(originalRowGroup); action < explorationInformation.getStartRowOfGroup(originalRowGroup + 1); ++action) { + if (includedChoicesIt != includedChoicesIte) { + STORM_LOG_TRACE("Next (local) choice contained in MEC is " << (*includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first])); + STORM_LOG_TRACE("Current (local) choice iterated is " << (action - explorationInformation.getStartRowOfGroup(originalRowGroup))); + if (action - explorationInformation.getStartRowOfGroup(originalRowGroup) != *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { + STORM_LOG_TRACE("Choice leaves the EC."); leavingChoices->insert(action); + } else { + STORM_LOG_TRACE("Choice stays in the EC."); + ++includedChoicesIt; } + } else { + STORM_LOG_TRACE("Choice leaves the EC, because there is no more choice staying in the EC."); + leavingChoices->insert(action); } - - explorationInformation.stateToLeavingActionsOfEndComponent[originalState] = leavingChoices; } - // If one of the states of the EC is a target state, all states in the EC have probability 1. - if (containsTargetState) { - STORM_LOG_TRACE("MEC contains a target state."); - for (auto const& stateAndChoices : mec) { - // Compute the state of the original model that corresponds to the current state. - StateType const& originalState = relevantStates[stateAndChoices.first]; - - STORM_LOG_TRACE("Setting lower bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 1."); - bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one()); - explorationInformation.addTerminalState(originalState); - } - } else if (leavingChoices->empty()) { - STORM_LOG_TRACE("MEC's leaving choices are empty."); - // If there is no choice leaving the EC, but it contains no target state, all states have probability 0. - for (auto const& stateAndChoices : mec) { - // Compute the state of the original model that corresponds to the current state. - StateType const& originalState = relevantStates[stateAndChoices.first]; - - STORM_LOG_TRACE("Setting upper bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 0."); - bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); - explorationInformation.addTerminalState(originalState); - } + explorationInformation.stateToLeavingActionsOfEndComponent[originalState] = leavingChoices; + } + + // If one of the states of the EC is a target state, all states in the EC have probability 1. + if (containsTargetState) { + STORM_LOG_TRACE("MEC contains a target state."); + for (auto const& stateAndChoices : mec) { + // Compute the state of the original model that corresponds to the current state. + StateType const& originalState = relevantStates[stateAndChoices.first]; + + STORM_LOG_TRACE("Setting lower bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 1."); + bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one()); + explorationInformation.addTerminalState(originalState); + } + } else if (leavingChoices->empty()) { + STORM_LOG_TRACE("MEC's leaving choices are empty."); + // If there is no choice leaving the EC, but it contains no target state, all states have probability 0. + for (auto const& stateAndChoices : mec) { + // Compute the state of the original model that corresponds to the current state. + StateType const& originalState = relevantStates[stateAndChoices.first]; + + STORM_LOG_TRACE("Setting upper bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 0."); + bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); + explorationInformation.addTerminalState(originalState); } } } + template + void SparseMdpLearningModelChecker::analyzeMecForMinimalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { + // For minimal probabilities, all found MECs are guaranteed to not contain a target state. Hence, in all + // associated states, the probability is 0 and we can set the upper bounds of the states to 0). + + for (auto const& stateAndChoices : mec) { + // Compute the state of the original model that corresponds to the current state. + StateType originalState = relevantStates[stateAndChoices.first]; + + bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); + explorationInformation.addTerminalState(originalState); + } + } + template ValueType SparseMdpLearningModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { ValueType result = storm::utility::zero(); @@ -525,7 +572,7 @@ namespace storm { bounds.setBoundsForAction(action, newBoundsForAction); // Check if we need to update the values for the states. - if (explorationInformation.optimizationDirection == storm::OptimizationDirection::Maximize) { + if (explorationInformation.maximize()) { bounds.setLowerBoundOfStateIfGreaterThanOld(state, explorationInformation, newBoundsForAction.first); StateType rowGroup = explorationInformation.getRowGroup(state); @@ -542,7 +589,8 @@ namespace storm { StateType rowGroup = explorationInformation.getRowGroup(state); if (bounds.getLowerBoundForRowGroup(rowGroup) < newBoundsForAction.first) { if (explorationInformation.getRowGroupSize(rowGroup) > 1) { - newBoundsForAction.first = std::min(newBoundsForAction.first, computeBoundOverAllOtherActions(storm::OptimizationDirection::Maximize, state, action, explorationInformation, bounds)); + ValueType min = computeBoundOverAllOtherActions(storm::OptimizationDirection::Minimize, state, action, explorationInformation, bounds); + newBoundsForAction.first = std::min(newBoundsForAction.first, min); } bounds.setLowerBoundForRowGroup(rowGroup, newBoundsForAction.first); diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index c3a931913..b0a09e343 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -21,6 +21,8 @@ namespace storm { template class StateStorage; } + + class MaximalEndComponent; } namespace generator { @@ -49,7 +51,7 @@ namespace storm { private: // A struct that keeps track of certain statistics during the computation. struct Statistics { - Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), pathLengthUntilEndComponentDetection(27) { + Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), pathLengthUntilEndComponentDetection(10000) { // Intentionally left empty. } @@ -170,6 +172,10 @@ namespace storm { return rowGroupIndices[group + 1] - rowGroupIndices[group]; } + bool onlyOneActionAvailable(StateType const& group) const { + return getRowGroupSize(group) == 1; + } + void addTerminalState(StateType const& state) { terminalStates.insert(state); } @@ -185,6 +191,14 @@ namespace storm { void addRowsToMatrix(std::size_t const& count) { matrix.resize(matrix.size() + count); } + + bool maximize() const { + return optimizationDirection == storm::OptimizationDirection::Maximize; + } + + bool minimize() const { + return !maximize(); + } }; // A struct containg the lower and upper bounds per state and action. @@ -241,6 +255,14 @@ namespace storm { 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) { std::pair bounds = getBoundsForState(state, explorationInformation); return bounds.second - bounds.first; @@ -312,12 +334,16 @@ namespace storm { bool exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; - uint32_t sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; + ActionType sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation) const; void detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds) const; + void analyzeMecForMaximalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const; + + void analyzeMecForMinimalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const; + void updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; void updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; From ce91fa7d5b42b7f992167a50f65b4a7b7a7c9a24 Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 7 Apr 2016 16:58:48 +0200 Subject: [PATCH 20/31] started to work on local EC-detection Former-commit-id: 0f36a1bf786b6ed48e99f84e54c8acc3520ffa09 --- .../SparseMdpLearningModelChecker.cpp | 26 +++++--- .../SparseMdpLearningModelChecker.h | 11 +++- src/settings/SettingsManager.cpp | 5 ++ src/settings/SettingsManager.h | 7 +++ src/settings/modules/LearningSettings.cpp | 52 ++++++++++++++++ src/settings/modules/LearningSettings.h | 59 +++++++++++++++++++ 6 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 src/settings/modules/LearningSettings.cpp create mode 100644 src/settings/modules/LearningSettings.h diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index f8adee32e..d76638997 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -13,6 +13,7 @@ #include "src/settings/SettingsManager.h" #include "src/settings/modules/GeneralSettings.h" +#include "src/settings/modules/LearningSettings.h" #include "src/utility/macros.h" #include "src/exceptions/InvalidOperationException.h" @@ -42,7 +43,7 @@ namespace storm { StateGeneration stateGeneration(program, variableInformation, getTargetStateExpression(subformula)); - ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true)); + ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::learningSettings().isLocalECDetectionSet()); explorationInformation.optimizationDirection = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; // The first row group starts at action 0. @@ -361,18 +362,29 @@ namespace storm { // Outline: // 1. construct a sparse transition matrix of the relevant part of the state space. // 2. use this matrix to compute an MEC decomposition. - // 3. if non-empty analyze the decomposition for accepting/rejecting MECs. + // 3. if non-empty, analyze the decomposition for accepting/rejecting MECs. // Start with 1. storm::storage::SparseMatrixBuilder builder(0, 0, 0, false, true, 0); // Determine the set of states that was expanded. std::vector relevantStates; - for (StateType state = 0; state < explorationInformation.stateStorage.numberOfStates; ++state) { - // Add the state to the relevant states if it's unexplored. Additionally, if we are computing minimal - // probabilities, we only consider it relevant if it's not a target state. - if (!explorationInformation.isUnexplored(state) && (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(state, explorationInformation)))) { - relevantStates.push_back(state); + if (explorationInformation.useLocalECDetection()) { + for (auto const& stateActionPair : stack) { + if (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(stateActionPair.first, explorationInformation))) { + relevantStates.push_back(stateActionPair.first); + } + } + std::sort(relevantStates.begin(), relevantStates.end()); + auto newEnd = std::unique(relevantStates.begin(), relevantStates.end()); + relevantStates.resize(std::distance(relevantStates.begin(), newEnd)); + } else { + for (StateType state = 0; state < explorationInformation.stateStorage.numberOfStates; ++state) { + // Add the state to the relevant states if it's unexplored. Additionally, if we are computing minimal + // probabilities, we only consider it relevant if it's not a target state. + if (!explorationInformation.isUnexplored(state) && (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(state, explorationInformation)))) { + relevantStates.push_back(state); + } } } StateType sink = relevantStates.size(); diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index b0a09e343..df7c0ad92 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -87,7 +87,7 @@ namespace storm { // A structure containing the data assembled during exploration. struct ExplorationInformation { - ExplorationInformation(uint_fast64_t bitsPerBucket, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker) { + ExplorationInformation(uint_fast64_t bitsPerBucket, bool localECDetection, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localECDetection(localECDetection) { // Intentionally left empty. } @@ -104,6 +104,7 @@ namespace storm { StateSet terminalStates; std::unordered_map stateToLeavingActionsOfEndComponent; + bool localECDetection; void setInitialStates(std::vector const& initialStates) { stateStorage.initialStateIndices = initialStates; @@ -199,6 +200,14 @@ namespace storm { bool minimize() const { return !maximize(); } + + bool useLocalECDetection() const { + return localECDetection; + } + + bool useGlobalECDetection() const { + return !useLocalECDetection(); + } }; // A struct containg the lower and upper bounds per state and action. diff --git a/src/settings/SettingsManager.cpp b/src/settings/SettingsManager.cpp index fffb10366..65ab09d2a 100644 --- a/src/settings/SettingsManager.cpp +++ b/src/settings/SettingsManager.cpp @@ -26,6 +26,7 @@ #include "src/settings/modules/ParametricSettings.h" #include "src/settings/modules/SparseDtmcEliminationModelCheckerSettings.h" #include "src/settings/modules/TopologicalValueIterationEquationSolverSettings.h" +#include "src/settings/modules/LearningSettings.h" #include "src/utility/macros.h" #include "src/settings/Option.h" @@ -547,5 +548,9 @@ namespace storm { storm::settings::modules::SparseDtmcEliminationModelCheckerSettings const& sparseDtmcEliminationModelCheckerSettings() { return dynamic_cast(manager().getModule(storm::settings::modules::SparseDtmcEliminationModelCheckerSettings::moduleName)); } + + storm::settings::modules::LearningSettings const& learningSettings() { + return dynamic_cast(manager().getModule(storm::settings::modules::LearningSettings::moduleName)); + } } } \ No newline at end of file diff --git a/src/settings/SettingsManager.h b/src/settings/SettingsManager.h index 8d6f79964..52bc470d3 100644 --- a/src/settings/SettingsManager.h +++ b/src/settings/SettingsManager.h @@ -25,6 +25,7 @@ namespace storm { class TopologicalValueIterationEquationSolverSettings; class ParametricSettings; class SparseDtmcEliminationModelCheckerSettings; + class LearningSettings; class ModuleSettings; } class Option; @@ -330,6 +331,12 @@ namespace storm { * @return An object that allows accessing the settings of the elimination-based DTMC model checker. */ storm::settings::modules::SparseDtmcEliminationModelCheckerSettings const& sparseDtmcEliminationModelCheckerSettings(); + + /* Retrieves the settings of the learning engine. + * + * @return An object that allows accessing the settings of the learning engine. + */ + storm::settings::modules::LearningSettings const& learningSettings(); } // namespace settings } // namespace storm diff --git a/src/settings/modules/LearningSettings.cpp b/src/settings/modules/LearningSettings.cpp new file mode 100644 index 000000000..f393751f0 --- /dev/null +++ b/src/settings/modules/LearningSettings.cpp @@ -0,0 +1,52 @@ +#include "src/settings/modules/LearningSettings.h" +#include "src/settings/modules/GeneralSettings.h" +#include "src/settings/Option.h" +#include "src/settings/OptionBuilder.h" +#include "src/settings/ArgumentBuilder.h" +#include "src/settings/Argument.h" +#include "src/settings/SettingsManager.h" + +namespace storm { + namespace settings { + namespace modules { + + const std::string LearningSettings::moduleName = "learning"; + const std::string LearningSettings::ecDetectionTypeOptionName = "ecdetection"; + + LearningSettings::LearningSettings(storm::settings::SettingsManager& settingsManager) : ModuleSettings(settingsManager, moduleName) { + std::vector types = { "local", "global" }; + this->addOption(storm::settings::OptionBuilder(moduleName, ecDetectionTypeOptionName, true, "Sets the kind of EC-detection used. Available are: { local, global }.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(types)).setDefaultValueString("local").build()).build()); + } + + bool LearningSettings::isLocalECDetectionSet() const { + if (this->getOption(ecDetectionTypeOptionName).getArgumentByName("name").getValueAsString() == "local") { + return true; + } + return false; + } + + bool LearningSettings::isGlobalECDetectionSet() const { + if (this->getOption(ecDetectionTypeOptionName).getArgumentByName("name").getValueAsString() == "global") { + return true; + } + return false; + } + + LearningSettings::ECDetectionType LearningSettings::getECDetectionType() const { + std::string typeAsString = this->getOption(ecDetectionTypeOptionName).getArgumentByName("name").getValueAsString(); + if (typeAsString == "local") { + return LearningSettings::ECDetectionType::Local; + } else if (typeAsString == "global") { + return LearningSettings::ECDetectionType::Global; + } + STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown EC-detection type '" << typeAsString << "'."); + } + + bool LearningSettings::check() const { + bool optionsSet = this->getOption(ecDetectionTypeOptionName).getHasOptionBeenSet(); + STORM_LOG_WARN_COND(storm::settings::generalSettings().getEngine() == storm::settings::modules::GeneralSettings::Engine::Learning || !optionsSet, "Bisimulation minimization is not selected, so setting options for bisimulation has no effect."); + return true; + } + } // namespace modules + } // namespace settings +} // namespace storm \ No newline at end of file diff --git a/src/settings/modules/LearningSettings.h b/src/settings/modules/LearningSettings.h new file mode 100644 index 000000000..d95b9fe98 --- /dev/null +++ b/src/settings/modules/LearningSettings.h @@ -0,0 +1,59 @@ +#ifndef STORM_SETTINGS_MODULES_LEARNINGSETTINGS_H_ +#define STORM_SETTINGS_MODULES_LEARNINGSETTINGS_H_ + +#include "src/settings/modules/ModuleSettings.h" + +namespace storm { + namespace settings { + namespace modules { + + /*! + * This class represents the learning settings. + */ + class LearningSettings : public ModuleSettings { + public: + // An enumeration of all available EC-detection types. + enum class ECDetectionType { Local, Global }; + + /*! + * Creates a new set of learning settings that is managed by the given manager. + * + * @param settingsManager The responsible manager. + */ + LearningSettings(storm::settings::SettingsManager& settingsManager); + + /*! + * Retrieves whether local EC-detection is to be used. + * + * @return True iff local EC-detection is to be used. + */ + bool isLocalECDetectionSet() const; + + /*! + * Retrieves whether global EC-detection is to be used. + * + * @return True iff global EC-detection is to be used. + */ + bool isGlobalECDetectionSet() const; + + /*! + * Retrieves the selected EC-detection type. + * + * @return The selected EC-detection type. + */ + ECDetectionType getECDetectionType() const; + + virtual bool check() const override; + + // The name of the module. + static const std::string moduleName; + + private: + // Define the string names of the options as constants. + static const std::string ecDetectionTypeOptionName; + }; + } // namespace modules + } // namespace settings +} // namespace storm + +#endif /* STORM_SETTINGS_MODULES_LEARNINGSETTINGS_H_ */ From f1105aac2ab19858165ded6a615b1e887abd458b Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 8 Apr 2016 16:15:05 +0200 Subject: [PATCH 21/31] EC-detection appears to work now Former-commit-id: 0bb1369b3e7dc64682440985149c28f477ecfe51 --- .../SparseMdpLearningModelChecker.cpp | 120 +++++++++++------- .../SparseMdpLearningModelChecker.h | 24 +++- src/settings/SettingsManager.cpp | 1 + 3 files changed, 92 insertions(+), 53 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index d76638997..60196c639 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -23,8 +23,7 @@ namespace storm { namespace modelchecker { template - SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions) : program(storm::utility::prism::preprocessProgram(program, constantDefinitions)), variableInformation(this->program), //randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), - comparator(1e-9) { + SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions) : program(storm::utility::prism::preprocessProgram(program, constantDefinitions)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator() { // Intentionally left empty. } @@ -120,7 +119,7 @@ namespace storm { STORM_LOG_DEBUG("Value of initial state is in [" << bounds.getLowerBoundForState(initialStateIndex, explorationInformation) << ", " << bounds.getUpperBoundForState(initialStateIndex, explorationInformation) << "]."); ValueType difference = bounds.getDifferenceOfStateBounds(initialStateIndex, explorationInformation); STORM_LOG_DEBUG("Difference after iteration " << stats.iterations << " is " << difference << "."); - convergenceCriterionMet = difference < 1e-6; + convergenceCriterionMet = comparator.isZero(difference); ++stats.iterations; } @@ -128,8 +127,9 @@ namespace storm { if (storm::settings::generalSettings().isShowStatisticsSet()) { std::cout << std::endl << "Learning summary -------------------------" << std::endl; std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target)" << std::endl; - std::cout << "Sampling iterations: " << stats.iterations << std::endl; + std::cout << "Sampled paths: " << stats.iterations << std::endl; std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; + std::cout << "EC detections: " << stats.ecDetections << " (" << stats.failedEcDetections << " failed, " << stats.totalNumberOfEcDetected << " EC(s) detected)" << std::endl; } return std::make_tuple(initialStateIndex, bounds.getLowerBoundForState(initialStateIndex, explorationInformation), bounds.getUpperBoundForState(initialStateIndex, explorationInformation)); @@ -180,13 +180,16 @@ namespace storm { // If the current path length exceeds the threshold and the model is a nondeterministic one, we // perform an EC detection. - if (stack.size() > stats.pathLengthUntilEndComponentDetection && !program.isDeterministicModel()) { - detectEndComponents(stack, explorationInformation, bounds); + if (stack.size() > explorationInformation.getPathLengthUntilEndComponentDetection()) { + bool success = detectEndComponents(stack, explorationInformation, bounds, stats); - // Abort the current search. - STORM_LOG_TRACE("Aborting the search after EC detection."); - stack.clear(); - break; + // Only if the detection found an EC, we abort the search. + if (success) { + // Abort the current search. + STORM_LOG_TRACE("Aborting the search after succesful EC detection."); + stack.clear(); + break; + } } } } @@ -296,31 +299,17 @@ namespace storm { // Determine the values of all available actions. std::vector> actionValues; StateType rowGroup = explorationInformation.getRowGroup(currentStateId); - auto choicesInEcIt = explorationInformation.stateToLeavingActionsOfEndComponent.find(currentStateId); // Check for cases in which we do not need to perform more work. - if (choicesInEcIt == explorationInformation.stateToLeavingActionsOfEndComponent.end()) { - if (explorationInformation.onlyOneActionAvailable(rowGroup)) { - return explorationInformation.getStartRowOfGroup(rowGroup); - } - } else { - if (choicesInEcIt->second->size() == 1) { - return *choicesInEcIt->second->begin(); - } + if (explorationInformation.onlyOneActionAvailable(rowGroup)) { + return explorationInformation.getStartRowOfGroup(rowGroup); } // If there are more choices to consider, start by gathering the values of relevant actions. - if (choicesInEcIt != explorationInformation.stateToLeavingActionsOfEndComponent.end()) { - STORM_LOG_TRACE("Sampling from actions leaving the previously detected EC."); - for (auto const& row : *choicesInEcIt->second) { - actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.optimizationDirection, row))); - } - } else { - STORM_LOG_TRACE("Sampling from actions leaving the state."); - - for (uint32_t row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) { - actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.optimizationDirection, row))); - } + STORM_LOG_TRACE("Sampling from actions leaving the state."); + + for (uint32_t row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) { + actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.optimizationDirection, row))); } STORM_LOG_ASSERT(!actionValues.empty(), "Values for actions must not be empty."); @@ -356,8 +345,9 @@ namespace storm { } template - void SparseMdpLearningModelChecker::detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds) const { - STORM_LOG_TRACE("Starting EC detection."); + bool SparseMdpLearningModelChecker::detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { + STORM_LOG_TRACE("Starting " << (explorationInformation.useLocalECDetection() ? "local" : "global") << "EC detection."); + ++stats.ecDetections; // Outline: // 1. construct a sparse transition matrix of the relevant part of the state space. @@ -428,19 +418,29 @@ namespace storm { storm::storage::MaximalEndComponentDecomposition mecDecomposition(relevantStatesMatrix, relevantStatesMatrix.transpose(true)); STORM_LOG_TRACE("Successfully computed MEC decomposition. Found " << (mecDecomposition.size() > 1 ? (mecDecomposition.size() - 1) : 0) << " MEC(s)."); - // 3. Analyze the MEC decomposition. - for (auto const& mec : mecDecomposition) { - // Ignore the (expected) MEC of the sink state. - if (mec.containsState(sink)) { - continue; - } + // If the decomposition contains only the MEC consisting of the sink state, we count it as 'failed'. + if (mecDecomposition.size() <= 1) { + ++stats.failedEcDetections; +// explorationInformation.increasePathLengthUntilEndComponentDetection(); + return false; + } else { + stats.totalNumberOfEcDetected += mecDecomposition.size() - 1; - if (explorationInformation.maximize()) { - analyzeMecForMaximalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); - } else { - analyzeMecForMinimalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); + // 3. Analyze the MEC decomposition. + for (auto const& mec : mecDecomposition) { + // Ignore the (expected) MEC of the sink state. + if (mec.containsState(sink)) { + continue; + } + + if (explorationInformation.maximize()) { + analyzeMecForMaximalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); + } else { + analyzeMecForMinimalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); + } } } + return true; } template @@ -453,7 +453,7 @@ namespace storm { bool containsTargetState = false; // Now we record all choices leaving the EC. - ActionSetPointer leavingChoices = std::make_shared(); + std::vector leavingActions; for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. StateType originalState = relevantStates[stateAndChoices.first]; @@ -473,18 +473,16 @@ namespace storm { STORM_LOG_TRACE("Current (local) choice iterated is " << (action - explorationInformation.getStartRowOfGroup(originalRowGroup))); if (action - explorationInformation.getStartRowOfGroup(originalRowGroup) != *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) { STORM_LOG_TRACE("Choice leaves the EC."); - leavingChoices->insert(action); + leavingActions.push_back(action); } else { STORM_LOG_TRACE("Choice stays in the EC."); ++includedChoicesIt; } } else { STORM_LOG_TRACE("Choice leaves the EC, because there is no more choice staying in the EC."); - leavingChoices->insert(action); + leavingActions.push_back(action); } } - - explorationInformation.stateToLeavingActionsOfEndComponent[originalState] = leavingChoices; } // If one of the states of the EC is a target state, all states in the EC have probability 1. @@ -498,7 +496,7 @@ namespace storm { bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one()); explorationInformation.addTerminalState(originalState); } - } else if (leavingChoices->empty()) { + } else if (leavingActions.empty()) { STORM_LOG_TRACE("MEC's leaving choices are empty."); // If there is no choice leaving the EC, but it contains no target state, all states have probability 0. for (auto const& stateAndChoices : mec) { @@ -509,6 +507,32 @@ namespace storm { bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); explorationInformation.addTerminalState(originalState); } + } else { + // In this case, no target state is contained in the MEC, but there are actions leaving the MEC. To + // prevent the simulation getting stuck in this MEC again, we replace all states in the MEC by a new + // state whose outgoing actions are the ones leaving the MEC. We do this, by assigning all states in the + // MEC to a new row group, which will then hold all the outgoing choices. + + // Remap all contained states to the new row group. + StateType nextRowGroup = explorationInformation.getNextRowGroup(); + for (auto const& stateAndChoices : mec) { + explorationInformation.assignStateToRowGroup(stateAndChoices.first, nextRowGroup); + } + + bounds.initializeBoundsForNextState(); + + // Add to the new row group all leaving actions of contained states and set the appropriate bounds for + // the actions and the new state. + std::pair stateBounds = getLowestBounds(explorationInformation.optimizationDirection); + for (auto const& action : leavingActions) { + explorationInformation.matrix.emplace_back(std::move(explorationInformation.matrix[action])); + std::pair const& actionBounds = bounds.getBoundsForAction(action); + bounds.initializeBoundsForNextAction(actionBounds); + stateBounds = combineBounds(explorationInformation.optimizationDirection, stateBounds, actionBounds); + } + + // Terminate the row group of the newly introduced state. + explorationInformation.rowGroupIndices.push_back(explorationInformation.matrix.size()); } } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index df7c0ad92..9dba78bba 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -51,7 +51,7 @@ namespace storm { private: // A struct that keeps track of certain statistics during the computation. struct Statistics { - Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), pathLengthUntilEndComponentDetection(10000) { + Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), ecDetections(0), failedEcDetections(0), totalNumberOfEcDetected(0) { // Intentionally left empty. } @@ -59,7 +59,9 @@ namespace storm { std::size_t maxPathLength; std::size_t numberOfTargetStates; std::size_t numberOfExploredStates; - std::size_t pathLengthUntilEndComponentDetection; + std::size_t ecDetections; + std::size_t failedEcDetections; + std::size_t totalNumberOfEcDetected; }; // A struct containing the data required for state exploration. @@ -87,7 +89,7 @@ namespace storm { // A structure containing the data assembled during exploration. struct ExplorationInformation { - ExplorationInformation(uint_fast64_t bitsPerBucket, bool localECDetection, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localECDetection(localECDetection) { + ExplorationInformation(uint_fast64_t bitsPerBucket, bool localECDetection, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localECDetection(localECDetection), pathLengthUntilEndComponentDetection(10000) { // Intentionally left empty. } @@ -103,8 +105,8 @@ namespace storm { storm::OptimizationDirection optimizationDirection; StateSet terminalStates; - std::unordered_map stateToLeavingActionsOfEndComponent; bool localECDetection; + uint_fast64_t pathLengthUntilEndComponentDetection; void setInitialStates(std::vector const& initialStates) { stateStorage.initialStateIndices = initialStates; @@ -133,6 +135,10 @@ namespace storm { return stateToRowGroupMapping[state]; } + StateType getNextRowGroup() const { + return rowGroupIndices.size() - 1; + } + void newRowGroup(ActionType const& action) { rowGroupIndices.push_back(action); } @@ -201,6 +207,14 @@ namespace storm { return !maximize(); } + uint_fast64_t getPathLengthUntilEndComponentDetection() const { + return pathLengthUntilEndComponentDetection; + } + + void increasePathLengthUntilEndComponentDetection() { + pathLengthUntilEndComponentDetection += 100; + } + bool useLocalECDetection() const { return localECDetection; } @@ -347,7 +361,7 @@ namespace storm { StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation) const; - void detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds) const; + bool detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; void analyzeMecForMaximalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const; diff --git a/src/settings/SettingsManager.cpp b/src/settings/SettingsManager.cpp index 65ab09d2a..effeaa658 100644 --- a/src/settings/SettingsManager.cpp +++ b/src/settings/SettingsManager.cpp @@ -49,6 +49,7 @@ namespace storm { this->addModule(std::unique_ptr(new modules::TopologicalValueIterationEquationSolverSettings(*this))); this->addModule(std::unique_ptr(new modules::ParametricSettings(*this))); this->addModule(std::unique_ptr(new modules::SparseDtmcEliminationModelCheckerSettings(*this))); + this->addModule(std::unique_ptr(new modules::LearningSettings(*this))); } SettingsManager::~SettingsManager() { From 6d421a6fbe11b2b430caab9eef6d5b72c44fb93b Mon Sep 17 00:00:00 2001 From: dehnert Date: Sat, 9 Apr 2016 17:18:08 +0200 Subject: [PATCH 22/31] learning seems to work find on first larger example Former-commit-id: 706981a3625cbf4ada04bd25516b373b508b0925 --- src/builder/ExplicitPrismModelBuilder.cpp | 16 +- src/builder/ExplicitPrismModelBuilder.h | 2 - src/generator/CompressedState.cpp | 15 +- src/generator/CompressedState.h | 15 +- .../SparseMdpLearningModelChecker.cpp | 203 +++++++++--------- .../SparseMdpLearningModelChecker.h | 46 ++-- src/settings/modules/LearningSettings.cpp | 30 +-- src/settings/modules/LearningSettings.h | 32 +-- 8 files changed, 200 insertions(+), 159 deletions(-) diff --git a/src/builder/ExplicitPrismModelBuilder.cpp b/src/builder/ExplicitPrismModelBuilder.cpp index 4820e1652..ed65da9da 100644 --- a/src/builder/ExplicitPrismModelBuilder.cpp +++ b/src/builder/ExplicitPrismModelBuilder.cpp @@ -227,18 +227,6 @@ namespace storm { return result; } - template - storm::expressions::SimpleValuation ExplicitPrismModelBuilder::unpackStateIntoValuation(storm::storage::BitVector const& currentState) { - storm::expressions::SimpleValuation result(program.getManager().getSharedPointer()); - for (auto const& booleanVariable : variableInformation.booleanVariables) { - result.setBooleanValue(booleanVariable.variable, currentState.get(booleanVariable.bitOffset)); - } - for (auto const& integerVariable : variableInformation.integerVariables) { - result.setIntegerValue(integerVariable.variable, currentState.getAsInt(integerVariable.bitOffset, integerVariable.bitWidth) + integerVariable.lowerBound); - } - return result; - } - template StateType ExplicitPrismModelBuilder::getOrAddStateIndex(CompressedState const& state) { uint32_t newIndex = stateStorage.numberOfStates; @@ -343,7 +331,7 @@ namespace storm { ++currentRow; ++currentRowGroup; } else { - std::cout << unpackStateIntoValuation(currentState).toString(true) << std::endl; + std::cout << storm::generator::unpackStateIntoValuation(currentState, variableInformation, program.getManager()).toString(true) << std::endl; STORM_LOG_THROW(false, storm::exceptions::WrongFormatException, "Error while creating sparse matrix from probabilistic program: found deadlock state. For fixing these, please provide the appropriate option."); } @@ -481,7 +469,7 @@ namespace storm { if (options.buildStateValuations) { stateValuations = storm::storage::sparse::StateValuations(stateStorage.numberOfStates); for (auto const& bitVectorIndexPair : stateStorage.stateToId) { - stateValuations.get().valuations[bitVectorIndexPair.second] = unpackStateIntoValuation(bitVectorIndexPair.first); + stateValuations.get().valuations[bitVectorIndexPair.second] = unpackStateIntoValuation(bitVectorIndexPair.first, variableInformation, program.getManager()); } } diff --git a/src/builder/ExplicitPrismModelBuilder.h b/src/builder/ExplicitPrismModelBuilder.h index 8cc724de2..d790f0bb1 100644 --- a/src/builder/ExplicitPrismModelBuilder.h +++ b/src/builder/ExplicitPrismModelBuilder.h @@ -190,8 +190,6 @@ namespace storm { storm::prism::Program const& getTranslatedProgram() const; private: - storm::expressions::SimpleValuation unpackStateIntoValuation(storm::storage::BitVector const& currentState); - /*! * Retrieves the state id of the given state. If the state has not been encountered yet, it will be added to * the lists of all states with a new id. If the state was already known, the object that is pointed to by diff --git a/src/generator/CompressedState.cpp b/src/generator/CompressedState.cpp index c63504ebb..03ba3d4c7 100644 --- a/src/generator/CompressedState.cpp +++ b/src/generator/CompressedState.cpp @@ -1,6 +1,8 @@ #include "src/generator/CompressedState.h" #include "src/generator/VariableInformation.h" +#include "src/storage/expressions/ExpressionManager.h" +#include "src/storage/expressions/SimpleValuation.h" #include "src/storage/expressions/ExpressionEvaluator.h" namespace storm { @@ -14,10 +16,21 @@ namespace storm { for (auto const& integerVariable : variableInformation.integerVariables) { evaluator.setIntegerValue(integerVariable.variable, state.getAsInt(integerVariable.bitOffset, integerVariable.bitWidth) + integerVariable.lowerBound); } - + } + + storm::expressions::SimpleValuation unpackStateIntoValuation(CompressedState const& state, VariableInformation const& variableInformation, storm::expressions::ExpressionManager const& manager) { + storm::expressions::SimpleValuation result(manager.getSharedPointer()); + for (auto const& booleanVariable : variableInformation.booleanVariables) { + result.setBooleanValue(booleanVariable.variable, state.get(booleanVariable.bitOffset)); + } + for (auto const& integerVariable : variableInformation.integerVariables) { + result.setIntegerValue(integerVariable.variable, state.getAsInt(integerVariable.bitOffset, integerVariable.bitWidth) + integerVariable.lowerBound); + } + return result; } template void unpackStateIntoEvaluator(CompressedState const& state, VariableInformation const& variableInformation, storm::expressions::ExpressionEvaluator& evaluator); template void unpackStateIntoEvaluator(CompressedState const& state, VariableInformation const& variableInformation, storm::expressions::ExpressionEvaluator& evaluator); + storm::expressions::SimpleValuation unpackStateIntoValuation(CompressedState const& state, VariableInformation const& variableInformation, storm::expressions::ExpressionManager const& manager); } } \ No newline at end of file diff --git a/src/generator/CompressedState.h b/src/generator/CompressedState.h index 52f0b4a62..a1b6212bb 100644 --- a/src/generator/CompressedState.h +++ b/src/generator/CompressedState.h @@ -6,6 +6,9 @@ namespace storm { namespace expressions { template class ExpressionEvaluator; + + class ExpressionManager; + class SimpleValuation; } namespace generator { @@ -18,11 +21,21 @@ namespace storm { * Unpacks the compressed state into the evaluator. * * @param state The state to unpack. - * @param variableInformation The information about how the variables are packed with the state. + * @param variableInformation The information about how the variables are packed within the state. * @param evaluator The evaluator into which to load the state. */ template void unpackStateIntoEvaluator(CompressedState const& state, VariableInformation const& variableInformation, storm::expressions::ExpressionEvaluator& evaluator); + + /*! + * Converts the compressed state into an explicit representation in the form of a valuation. + * + * @param state The state to unpack. + * @param variableInformation The information about how the variables are packed within the state. + * @param manager The manager responsible for the variables. + * @return A valuation that corresponds to the compressed state. + */ + storm::expressions::SimpleValuation unpackStateIntoValuation(CompressedState const& state, VariableInformation const& variableInformation, storm::expressions::ExpressionManager const& manager); } } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 60196c639..2e3b6c601 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -2,6 +2,7 @@ #include "src/storage/SparseMatrix.h" #include "src/storage/MaximalEndComponentDecomposition.h" +#include "src/storage/expressions/SimpleValuation.h" #include "src/logic/FragmentSpecification.h" @@ -15,6 +16,8 @@ #include "src/settings/modules/GeneralSettings.h" #include "src/settings/modules/LearningSettings.h" +#include "src/utility/graph.h" + #include "src/utility/macros.h" #include "src/exceptions/InvalidOperationException.h" #include "src/exceptions/InvalidPropertyException.h" @@ -42,7 +45,7 @@ namespace storm { StateGeneration stateGeneration(program, variableInformation, getTargetStateExpression(subformula)); - ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::learningSettings().isLocalECDetectionSet()); + ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::learningSettings().isLocalPrecomputationSet(), storm::settings::learningSettings().getNumberOfExplorationStepsUntilPrecomputation()); explorationInformation.optimizationDirection = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; // The first row group starts at action 0. @@ -70,7 +73,7 @@ namespace storm { template std::function::StateType (storm::generator::CompressedState const&)> SparseMdpLearningModelChecker::createStateToIdCallback(ExplorationInformation& explorationInformation) const { - return [&explorationInformation] (storm::generator::CompressedState const& state) -> StateType { + return [&explorationInformation,this] (storm::generator::CompressedState const& state) -> StateType { StateType newIndex = explorationInformation.stateStorage.numberOfStates; // Check, if the state was already registered. @@ -118,16 +121,16 @@ namespace storm { STORM_LOG_DEBUG("Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored)."); STORM_LOG_DEBUG("Value of initial state is in [" << bounds.getLowerBoundForState(initialStateIndex, explorationInformation) << ", " << bounds.getUpperBoundForState(initialStateIndex, explorationInformation) << "]."); ValueType difference = bounds.getDifferenceOfStateBounds(initialStateIndex, explorationInformation); - STORM_LOG_DEBUG("Difference after iteration " << stats.iterations << " is " << difference << "."); + STORM_LOG_DEBUG("Difference after iteration " << stats.pathsSampled << " is " << difference << "."); convergenceCriterionMet = comparator.isZero(difference); - ++stats.iterations; + ++stats.pathsSampled; } if (storm::settings::generalSettings().isShowStatisticsSet()) { std::cout << std::endl << "Learning summary -------------------------" << std::endl; std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target)" << std::endl; - std::cout << "Sampled paths: " << stats.iterations << std::endl; + std::cout << "Sampled paths: " << stats.pathsSampled << std::endl; std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; std::cout << "EC detections: " << stats.ecDetections << " (" << stats.failedEcDetections << " failed, " << stats.totalNumberOfEcDetected << " EC(s) detected)" << std::endl; } @@ -154,6 +157,9 @@ namespace storm { // Explore the previously unexplored state. storm::generator::CompressedState const& compressedState = unexploredIt->second; foundTerminalState = exploreState(stateGeneration, currentStateId, compressedState, explorationInformation, bounds, stats); + if (foundTerminalState) { + STORM_LOG_TRACE("Aborting sampling of path, because a terminal state was reached."); + } explorationInformation.unexploredStates.erase(unexploredIt); } else { // If the state was already explored, we check whether it is a terminal state or not. @@ -163,6 +169,9 @@ namespace storm { } } + // Increase the number of exploration steps to eventually trigger the precomputation. + ++stats.explorationSteps; + // If the state was not a terminal state, we continue the path search and sample the next state. if (!foundTerminalState) { // At this point, we can be sure that the state was expanded and that we can sample according to the @@ -171,25 +180,20 @@ namespace storm { stack.back().second = chosenAction; STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << "."); - StateType successor = sampleSuccessorFromAction(chosenAction, explorationInformation); + StateType successor = sampleSuccessorFromAction(chosenAction, explorationInformation, bounds); STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << "."); // Put the successor state and a dummy action on top of the stack. stack.emplace_back(successor, 0); stats.maxPathLength = std::max(stats.maxPathLength, stack.size()); - // If the current path length exceeds the threshold and the model is a nondeterministic one, we - // perform an EC detection. - if (stack.size() > explorationInformation.getPathLengthUntilEndComponentDetection()) { - bool success = detectEndComponents(stack, explorationInformation, bounds, stats); + // If the number of exploration steps exceeds a certain threshold, do a precomputation. + if (explorationInformation.performPrecomputation(stats.explorationSteps)) { + performPrecomputation(stack, explorationInformation, bounds, stats); - // Only if the detection found an EC, we abort the search. - if (success) { - // Abort the current search. - STORM_LOG_TRACE("Aborting the search after succesful EC detection."); - stack.clear(); - break; - } + STORM_LOG_TRACE("Aborting the search after precomputation."); + stack.clear(); + break; } } } @@ -333,10 +337,10 @@ namespace storm { } template - typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation) const { + typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { // TODO: precompute this? std::vector probabilities(explorationInformation.getRowOfMatrix(chosenAction).size()); - std::transform(explorationInformation.getRowOfMatrix(chosenAction).begin(), explorationInformation.getRowOfMatrix(chosenAction).end(), probabilities.begin(), [] (storm::storage::MatrixEntry const& entry) { return entry.getValue(); } ); + std::transform(explorationInformation.getRowOfMatrix(chosenAction).begin(), explorationInformation.getRowOfMatrix(chosenAction).end(), probabilities.begin(), [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { return entry.getValue() * bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation) ; } ); // Now sample according to the probabilities. std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); @@ -345,21 +349,27 @@ namespace storm { } template - bool SparseMdpLearningModelChecker::detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { - STORM_LOG_TRACE("Starting " << (explorationInformation.useLocalECDetection() ? "local" : "global") << "EC detection."); - ++stats.ecDetections; - + bool SparseMdpLearningModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { // Outline: // 1. construct a sparse transition matrix of the relevant part of the state space. - // 2. use this matrix to compute an MEC decomposition. - // 3. if non-empty, analyze the decomposition for accepting/rejecting MECs. + // 2. use this matrix to compute states with probability 0/1 and an MEC decomposition (in the max case). + // 3. use MEC decomposition to collapse MECs. + STORM_LOG_TRACE("Starting " << (explorationInformation.useLocalPrecomputation() ? "local" : "global") << " precomputation."); + + for (int row = 0; row < explorationInformation.matrix.size(); ++row) { + std::cout << "row " << row << ":"; + for (auto const& el : explorationInformation.matrix[row]) { + std::cout << el.getColumn() << ", " << el.getValue() << " [" << bounds.getLowerBoundForState(el.getColumn(), explorationInformation) << ", " << bounds.getUpperBoundForState(el.getColumn(), explorationInformation) << "]" << " "; + } + std::cout << std::endl; + } - // Start with 1. + // Construct the matrix that represents the fragment of the system contained in the currently sampled path. storm::storage::SparseMatrixBuilder builder(0, 0, 0, false, true, 0); // Determine the set of states that was expanded. std::vector relevantStates; - if (explorationInformation.useLocalECDetection()) { + if (explorationInformation.useLocalPrecomputation()) { for (auto const& stateActionPair : stack) { if (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(stateActionPair.first, explorationInformation))) { relevantStates.push_back(stateActionPair.first); @@ -380,9 +390,15 @@ namespace storm { StateType sink = relevantStates.size(); // Create a mapping for faster look-up during the translation of flexible matrix to the real sparse matrix. + // While doing so, record all target states. std::unordered_map relevantStateToNewRowGroupMapping; + storm::storage::BitVector targetStates(sink + 1); for (StateType index = 0; index < relevantStates.size(); ++index) { relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); + if (storm::utility::isOne(bounds.getLowerBoundForState(relevantStates[index], explorationInformation))) { + std::cout << "target states: " << targetStates << std::endl; + targetStates.set(index); + } } // Do the actual translation. @@ -411,48 +427,76 @@ namespace storm { // Then, make the unexpanded state absorbing. builder.newRowGroup(currentRow); builder.addNextValue(currentRow, sink, storm::utility::one()); - STORM_LOG_TRACE("Successfully built matrix for MEC decomposition."); - - // Go on to step 2. storm::storage::SparseMatrix relevantStatesMatrix = builder.build(); - storm::storage::MaximalEndComponentDecomposition mecDecomposition(relevantStatesMatrix, relevantStatesMatrix.transpose(true)); - STORM_LOG_TRACE("Successfully computed MEC decomposition. Found " << (mecDecomposition.size() > 1 ? (mecDecomposition.size() - 1) : 0) << " MEC(s)."); - - // If the decomposition contains only the MEC consisting of the sink state, we count it as 'failed'. - if (mecDecomposition.size() <= 1) { - ++stats.failedEcDetections; -// explorationInformation.increasePathLengthUntilEndComponentDetection(); - return false; - } else { - stats.totalNumberOfEcDetected += mecDecomposition.size() - 1; - - // 3. Analyze the MEC decomposition. - for (auto const& mec : mecDecomposition) { - // Ignore the (expected) MEC of the sink state. - if (mec.containsState(sink)) { - continue; - } + storm::storage::SparseMatrix transposedMatrix = relevantStatesMatrix.transpose(true); + STORM_LOG_TRACE("Successfully built matrix for precomputation."); + + storm::storage::BitVector allStates(sink + 1, true); + storm::storage::BitVector statesWithProbability0; + storm::storage::BitVector statesWithProbability1; + if (explorationInformation.maximize()) { + // If we are computing maximal probabilities, we first perform a detection of states that have + // probability 01 and then additionally perform an MEC decomposition. The reason for this somewhat + // duplicate work is the following. Optimally, we would only do the MEC decomposition, because we need + // it anyway. However, when only detecting (accepting) MECs, we do not infer which of the other states + // (not contained in MECs) also have probability 0/1. + statesWithProbability0 = storm::utility::graph::performProb0A(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); + targetStates.set(sink, true); + statesWithProbability1 = storm::utility::graph::performProb1E(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); + + storm::storage::MaximalEndComponentDecomposition mecDecomposition(relevantStatesMatrix, relevantStatesMatrix.transpose(true)); + ++stats.ecDetections; + STORM_LOG_TRACE("Successfully computed MEC decomposition. Found " << (mecDecomposition.size() > 1 ? (mecDecomposition.size() - 1) : 0) << " MEC(s)."); + + // If the decomposition contains only the MEC consisting of the sink state, we count it as 'failed'. + if (mecDecomposition.size() > 1) { + ++stats.failedEcDetections; + } else { + stats.totalNumberOfEcDetected += mecDecomposition.size() - 1; - if (explorationInformation.maximize()) { - analyzeMecForMaximalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); - } else { - analyzeMecForMinimalProbabilities(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); + // 3. Analyze the MEC decomposition. + for (auto const& mec : mecDecomposition) { + // Ignore the (expected) MEC of the sink state. + if (mec.containsState(sink)) { + continue; + } + + collapseMec(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds); } } + } else { + // If we are computing minimal probabilities, we do not need to perform an EC-detection. We rather + // compute all states (of the considered fragment) that have probability 0/1. For states with + // probability 0, we have to mark the sink as being a target. For states with probability 1, however, + // we must treat the sink as being rejecting. + targetStates.set(sink, true); + statesWithProbability0 = storm::utility::graph::performProb0E(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); + targetStates.set(sink, false); + statesWithProbability1 = storm::utility::graph::performProb1A(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); + } + + std::cout << "prob0: " << statesWithProbability0 << std::endl; + std::cout << "prob1: " << statesWithProbability1 << std::endl; + + // Set the bounds of the identified states. + for (auto state : statesWithProbability0) { + StateType originalState = relevantStates[state]; + bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); + explorationInformation.addTerminalState(originalState); + } + for (auto state : statesWithProbability1) { + StateType originalState = relevantStates[state]; + bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one()); + explorationInformation.addTerminalState(originalState); } return true; } template - void SparseMdpLearningModelChecker::analyzeMecForMaximalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { - // For maximal probabilities, we check (a) which MECs contain a target state, because the associated states - // have a probability of 1 (and we can therefore set their lower bounds to 1) and (b) which of the remaining - // MECs have no outgoing action, because the associated states have a probability of 0 (and we can therefore - // set their upper bounds to 0). - + void SparseMdpLearningModelChecker::collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { bool containsTargetState = false; - // Now we record all choices leaving the EC. + // Now we record all actions leaving the EC. std::vector leavingActions; for (auto const& stateAndChoices : mec) { // Compute the state of the original model that corresponds to the current state. @@ -485,34 +529,14 @@ namespace storm { } } - // If one of the states of the EC is a target state, all states in the EC have probability 1. - if (containsTargetState) { - STORM_LOG_TRACE("MEC contains a target state."); - for (auto const& stateAndChoices : mec) { - // Compute the state of the original model that corresponds to the current state. - StateType const& originalState = relevantStates[stateAndChoices.first]; - - STORM_LOG_TRACE("Setting lower bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 1."); - bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one()); - explorationInformation.addTerminalState(originalState); - } - } else if (leavingActions.empty()) { - STORM_LOG_TRACE("MEC's leaving choices are empty."); - // If there is no choice leaving the EC, but it contains no target state, all states have probability 0. - for (auto const& stateAndChoices : mec) { - // Compute the state of the original model that corresponds to the current state. - StateType const& originalState = relevantStates[stateAndChoices.first]; - - STORM_LOG_TRACE("Setting upper bound of state in row group " << explorationInformation.getRowGroup(originalState) << " to 0."); - bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); - explorationInformation.addTerminalState(originalState); - } - } else { + // Now, we need to collapse the EC only if it does not contain a target state and the leaving actions are + // non-empty, because only then have the states a (potentially) non-zero, non-one probability. + if (!containsTargetState && !leavingActions.empty()) { // In this case, no target state is contained in the MEC, but there are actions leaving the MEC. To // prevent the simulation getting stuck in this MEC again, we replace all states in the MEC by a new // state whose outgoing actions are the ones leaving the MEC. We do this, by assigning all states in the // MEC to a new row group, which will then hold all the outgoing choices. - + // Remap all contained states to the new row group. StateType nextRowGroup = explorationInformation.getNextRowGroup(); for (auto const& stateAndChoices : mec) { @@ -530,26 +554,13 @@ namespace storm { bounds.initializeBoundsForNextAction(actionBounds); stateBounds = combineBounds(explorationInformation.optimizationDirection, stateBounds, actionBounds); } + bounds.setBoundsForRowGroup(nextRowGroup, stateBounds); // Terminate the row group of the newly introduced state. explorationInformation.rowGroupIndices.push_back(explorationInformation.matrix.size()); } } - template - void SparseMdpLearningModelChecker::analyzeMecForMinimalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { - // For minimal probabilities, all found MECs are guaranteed to not contain a target state. Hence, in all - // associated states, the probability is 0 and we can set the upper bounds of the states to 0). - - for (auto const& stateAndChoices : mec) { - // Compute the state of the original model that corresponds to the current state. - StateType originalState = relevantStates[stateAndChoices.first]; - - bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); - explorationInformation.addTerminalState(originalState); - } - } - template ValueType SparseMdpLearningModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { ValueType result = storm::utility::zero(); diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 9dba78bba..d9a4baf7f 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -51,11 +51,12 @@ namespace storm { private: // A struct that keeps track of certain statistics during the computation. struct Statistics { - Statistics() : iterations(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), ecDetections(0), failedEcDetections(0), totalNumberOfEcDetected(0) { + Statistics() : pathsSampled(0), explorationSteps(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), ecDetections(0), failedEcDetections(0), totalNumberOfEcDetected(0) { // Intentionally left empty. } - std::size_t iterations; + std::size_t pathsSampled; + std::size_t explorationSteps; std::size_t maxPathLength; std::size_t numberOfTargetStates; std::size_t numberOfExploredStates; @@ -89,7 +90,7 @@ namespace storm { // A structure containing the data assembled during exploration. struct ExplorationInformation { - ExplorationInformation(uint_fast64_t bitsPerBucket, bool localECDetection, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localECDetection(localECDetection), pathLengthUntilEndComponentDetection(10000) { + ExplorationInformation(uint_fast64_t bitsPerBucket, bool localPrecomputation, uint_fast64_t numberOfExplorationStepsUntilPrecomputation, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localPrecomputation(localPrecomputation), numberOfExplorationStepsUntilPrecomputation(numberOfExplorationStepsUntilPrecomputation) { // Intentionally left empty. } @@ -105,8 +106,8 @@ namespace storm { storm::OptimizationDirection optimizationDirection; StateSet terminalStates; - bool localECDetection; - uint_fast64_t pathLengthUntilEndComponentDetection; + bool localPrecomputation; + uint_fast64_t numberOfExplorationStepsUntilPrecomputation; void setInitialStates(std::vector const& initialStates) { stateStorage.initialStateIndices = initialStates; @@ -207,20 +208,21 @@ namespace storm { return !maximize(); } - uint_fast64_t getPathLengthUntilEndComponentDetection() const { - return pathLengthUntilEndComponentDetection; - } - - void increasePathLengthUntilEndComponentDetection() { - pathLengthUntilEndComponentDetection += 100; + bool performPrecomputation(std::size_t& performedExplorationSteps) const { + bool result = performedExplorationSteps > numberOfExplorationStepsUntilPrecomputation; + if (result) { + std::cout << "triggering precomp" << std::endl; + performedExplorationSteps = 0; + } + return result; } - bool useLocalECDetection() const { - return localECDetection; + bool useLocalPrecomputation() const { + return localPrecomputation; } - bool useGlobalECDetection() const { - return !useLocalECDetection(); + bool useGlobalPrecomputation() const { + return !useLocalPrecomputation(); } }; @@ -286,7 +288,7 @@ namespace storm { } } - ValueType getDifferenceOfStateBounds(StateType const& state, ExplorationInformation const& explorationInformation) { + ValueType getDifferenceOfStateBounds(StateType const& state, ExplorationInformation const& explorationInformation) const { std::pair bounds = getBoundsForState(state, explorationInformation); return bounds.second - bounds.first; } @@ -324,6 +326,10 @@ namespace storm { void setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { StateType const& rowGroup = explorationInformation.getRowGroup(state); + setBoundsForRowGroup(rowGroup, values); + } + + void setBoundsForRowGroup(StateType const& rowGroup, std::pair const& values) { lowerBoundsPerState[rowGroup] = values.first; upperBoundsPerState[rowGroup] = values.second; } @@ -359,13 +365,11 @@ namespace storm { ActionType sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; - StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation) const; + StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - bool detectEndComponents(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; + bool performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; - void analyzeMecForMaximalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const; - - void analyzeMecForMinimalProbabilities(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const; + void collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const; void updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; diff --git a/src/settings/modules/LearningSettings.cpp b/src/settings/modules/LearningSettings.cpp index f393751f0..fdddd1672 100644 --- a/src/settings/modules/LearningSettings.cpp +++ b/src/settings/modules/LearningSettings.cpp @@ -11,40 +11,46 @@ namespace storm { namespace modules { const std::string LearningSettings::moduleName = "learning"; - const std::string LearningSettings::ecDetectionTypeOptionName = "ecdetection"; + const std::string LearningSettings::precomputationTypeOptionName = "precomp"; + const std::string LearningSettings::numberOfExplorationStepsUntilPrecomputationOptionName = "stepsprecomp"; LearningSettings::LearningSettings(storm::settings::SettingsManager& settingsManager) : ModuleSettings(settingsManager, moduleName) { std::vector types = { "local", "global" }; - this->addOption(storm::settings::OptionBuilder(moduleName, ecDetectionTypeOptionName, true, "Sets the kind of EC-detection used. Available are: { local, global }.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(types)).setDefaultValueString("local").build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, precomputationTypeOptionName, true, "Sets the kind of precomputation used. Available are: { local, global }.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(types)).setDefaultValueString("local").build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, numberOfExplorationStepsUntilPrecomputationOptionName, false, "Sets the number of exploration steps until a precomputation is triggered.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of exploration steps to perform.").setDefaultValueUnsignedInteger(100000).build()).build()); } - bool LearningSettings::isLocalECDetectionSet() const { - if (this->getOption(ecDetectionTypeOptionName).getArgumentByName("name").getValueAsString() == "local") { + bool LearningSettings::isLocalPrecomputationSet() const { + if (this->getOption(precomputationTypeOptionName).getArgumentByName("name").getValueAsString() == "local") { return true; } return false; } - bool LearningSettings::isGlobalECDetectionSet() const { - if (this->getOption(ecDetectionTypeOptionName).getArgumentByName("name").getValueAsString() == "global") { + bool LearningSettings::isGlobalPrecomputationSet() const { + if (this->getOption(precomputationTypeOptionName).getArgumentByName("name").getValueAsString() == "global") { return true; } return false; } - LearningSettings::ECDetectionType LearningSettings::getECDetectionType() const { - std::string typeAsString = this->getOption(ecDetectionTypeOptionName).getArgumentByName("name").getValueAsString(); + LearningSettings::PrecomputationType LearningSettings::getPrecomputationType() const { + std::string typeAsString = this->getOption(precomputationTypeOptionName).getArgumentByName("name").getValueAsString(); if (typeAsString == "local") { - return LearningSettings::ECDetectionType::Local; + return LearningSettings::PrecomputationType::Local; } else if (typeAsString == "global") { - return LearningSettings::ECDetectionType::Global; + return LearningSettings::PrecomputationType::Global; } STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown EC-detection type '" << typeAsString << "'."); } + uint_fast64_t LearningSettings::getNumberOfExplorationStepsUntilPrecomputation() const { + return this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getArgumentByName("count").getValueAsUnsignedInteger(); + } + bool LearningSettings::check() const { - bool optionsSet = this->getOption(ecDetectionTypeOptionName).getHasOptionBeenSet(); - STORM_LOG_WARN_COND(storm::settings::generalSettings().getEngine() == storm::settings::modules::GeneralSettings::Engine::Learning || !optionsSet, "Bisimulation minimization is not selected, so setting options for bisimulation has no effect."); + bool optionsSet = this->getOption(precomputationTypeOptionName).getHasOptionBeenSet() || this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getHasOptionBeenSet(); + STORM_LOG_WARN_COND(storm::settings::generalSettings().getEngine() == storm::settings::modules::GeneralSettings::Engine::Learning || !optionsSet, "Learning engine is not selected, so setting options for it has no effect."); return true; } } // namespace modules diff --git a/src/settings/modules/LearningSettings.h b/src/settings/modules/LearningSettings.h index d95b9fe98..8db1fe44a 100644 --- a/src/settings/modules/LearningSettings.h +++ b/src/settings/modules/LearningSettings.h @@ -12,8 +12,8 @@ namespace storm { */ class LearningSettings : public ModuleSettings { public: - // An enumeration of all available EC-detection types. - enum class ECDetectionType { Local, Global }; + // An enumeration of all available precomputation types. + enum class PrecomputationType { Local, Global }; /*! * Creates a new set of learning settings that is managed by the given manager. @@ -23,25 +23,32 @@ namespace storm { LearningSettings(storm::settings::SettingsManager& settingsManager); /*! - * Retrieves whether local EC-detection is to be used. + * Retrieves whether local precomputation is to be used. * - * @return True iff local EC-detection is to be used. + * @return True iff local precomputation is to be used. */ - bool isLocalECDetectionSet() const; + bool isLocalPrecomputationSet() const; /*! - * Retrieves whether global EC-detection is to be used. + * Retrieves whether global precomputation is to be used. * - * @return True iff global EC-detection is to be used. + * @return True iff global precomputation is to be used. */ - bool isGlobalECDetectionSet() const; + bool isGlobalPrecomputationSet() const; /*! - * Retrieves the selected EC-detection type. + * Retrieves the selected precomputation type. * - * @return The selected EC-detection type. + * @return The selected precomputation type. */ - ECDetectionType getECDetectionType() const; + PrecomputationType getPrecomputationType() const; + + /*! + * Retrieves the number of exploration steps to perform until a precomputation is triggered. + * + * @return The number of exploration steps to perform until a precomputation is triggered. + */ + uint_fast64_t getNumberOfExplorationStepsUntilPrecomputation() const; virtual bool check() const override; @@ -50,7 +57,8 @@ namespace storm { private: // Define the string names of the options as constants. - static const std::string ecDetectionTypeOptionName; + static const std::string precomputationTypeOptionName; + static const std::string numberOfExplorationStepsUntilPrecomputationOptionName; }; } // namespace modules } // namespace settings From 07e97e19774c7b1206574e47cd48d5384a7ff23d Mon Sep 17 00:00:00 2001 From: dehnert Date: Mon, 11 Apr 2016 14:37:46 +0200 Subject: [PATCH 23/31] added some statistics and options Former-commit-id: 1b6a9c20f6f88babeff3e63e53cf7b078347606c --- .../SparseMdpLearningModelChecker.cpp | 53 ++++-- .../SparseMdpLearningModelChecker.h | 157 +++++++++++++----- src/settings/modules/LearningSettings.cpp | 35 +++- src/settings/modules/LearningSettings.h | 26 +++ 4 files changed, 203 insertions(+), 68 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 2e3b6c601..f526ea27c 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -12,13 +12,10 @@ #include "src/models/sparse/StandardRewardModel.h" -#include "src/settings/SettingsManager.h" #include "src/settings/modules/GeneralSettings.h" -#include "src/settings/modules/LearningSettings.h" #include "src/utility/graph.h" -#include "src/utility/macros.h" #include "src/exceptions/InvalidOperationException.h" #include "src/exceptions/InvalidPropertyException.h" #include "src/exceptions/NotSupportedException.h" @@ -45,7 +42,7 @@ namespace storm { StateGeneration stateGeneration(program, variableInformation, getTargetStateExpression(subformula)); - ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::learningSettings().isLocalPrecomputationSet(), storm::settings::learningSettings().getNumberOfExplorationStepsUntilPrecomputation()); + ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::learningSettings().getNumberOfExplorationStepsUntilPrecomputation()); explorationInformation.optimizationDirection = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; // The first row group starts at action 0. @@ -107,6 +104,9 @@ namespace storm { while (!convergenceCriterionMet) { bool result = samplePathFromInitialState(stateGeneration, explorationInformation, stack, bounds, stats); + stats.sampledPath(); + stats.updateMaxPathLength(stack.size()); + // If a terminal state was found, we update the probabilities along the path contained in the stack. if (result) { // Update the bounds along the path to the terminal state. @@ -124,15 +124,15 @@ namespace storm { STORM_LOG_DEBUG("Difference after iteration " << stats.pathsSampled << " is " << difference << "."); convergenceCriterionMet = comparator.isZero(difference); - ++stats.pathsSampled; + // If the number of sampled paths exceeds a certain threshold, do a precomputation. + if (!convergenceCriterionMet && explorationInformation.performPrecomputationExcessiveSampledPaths(stats.pathsSampledSinceLastPrecomputation)) { + performPrecomputation(stack, explorationInformation, bounds, stats); + } } + // Show statistics if required. if (storm::settings::generalSettings().isShowStatisticsSet()) { - std::cout << std::endl << "Learning summary -------------------------" << std::endl; - std::cout << "Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, " << explorationInformation.getNumberOfUnexploredStates() << " unexplored, " << stats.numberOfTargetStates << " target)" << std::endl; - std::cout << "Sampled paths: " << stats.pathsSampled << std::endl; - std::cout << "Maximal path length: " << stats.maxPathLength << std::endl; - std::cout << "EC detections: " << stats.ecDetections << " (" << stats.failedEcDetections << " failed, " << stats.totalNumberOfEcDetected << " EC(s) detected)" << std::endl; + stats.printToStream(std::cout, explorationInformation); } return std::make_tuple(initialStateIndex, bounds.getLowerBoundForState(initialStateIndex, explorationInformation), bounds.getUpperBoundForState(initialStateIndex, explorationInformation)); @@ -169,8 +169,8 @@ namespace storm { } } - // Increase the number of exploration steps to eventually trigger the precomputation. - ++stats.explorationSteps; + // Notify the stats about the performed exploration step. + stats.explorationStep(); // If the state was not a terminal state, we continue the path search and sample the next state. if (!foundTerminalState) { @@ -185,10 +185,9 @@ namespace storm { // Put the successor state and a dummy action on top of the stack. stack.emplace_back(successor, 0); - stats.maxPathLength = std::max(stats.maxPathLength, stack.size()); // If the number of exploration steps exceeds a certain threshold, do a precomputation. - if (explorationInformation.performPrecomputation(stats.explorationSteps)) { + if (explorationInformation.performPrecomputationExcessiveExplorationSteps(stats.explorationStepsSinceLastPrecomputation)) { performPrecomputation(stack, explorationInformation, bounds, stats); STORM_LOG_TRACE("Aborting the search after precomputation."); @@ -338,18 +337,36 @@ namespace storm { template typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { - // TODO: precompute this? - std::vector probabilities(explorationInformation.getRowOfMatrix(chosenAction).size()); - std::transform(explorationInformation.getRowOfMatrix(chosenAction).begin(), explorationInformation.getRowOfMatrix(chosenAction).end(), probabilities.begin(), [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { return entry.getValue() * bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation) ; } ); + std::vector> const& row = explorationInformation.getRowOfMatrix(chosenAction); +// if (row.size() == 1) { +// return row.front().getColumn(); +// } + + std::vector probabilities(row.size()); + + // Depending on the selected next-state heuristic, we give the states other likelihoods of getting chosen. + if (explorationInformation.useDifferenceWeightedProbabilityHeuristic()) { + std::transform(row.begin(), row.end(), probabilities.begin(), + [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { + return entry.getValue() * bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation); + }); + } else if (explorationInformation.useProbabilityHeuristic()) { + std::transform(row.begin(), row.end(), probabilities.begin(), + [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { + return entry.getValue(); + }); + } // Now sample according to the probabilities. std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); StateType offset = distribution(randomGenerator); - return explorationInformation.getRowOfMatrix(chosenAction)[offset].getColumn(); + return row[offset].getColumn(); } template bool SparseMdpLearningModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { + ++stats.numberOfPrecomputations; + // Outline: // 1. construct a sparse transition matrix of the relevant part of the state space. // 2. use this matrix to compute states with probability 0/1 and an MEC decomposition (in the max case). diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index d9a4baf7f..2e43ad582 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -12,6 +12,10 @@ #include "src/generator/CompressedState.h" #include "src/generator/VariableInformation.h" +#include "src/settings/SettingsManager.h" +#include "src/settings/modules/LearningSettings.h" + +#include "src/utility/macros.h" #include "src/utility/ConstantsComparator.h" #include "src/utility/constants.h" @@ -49,49 +53,18 @@ namespace storm { virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; private: - // A struct that keeps track of certain statistics during the computation. - struct Statistics { - Statistics() : pathsSampled(0), explorationSteps(0), maxPathLength(0), numberOfTargetStates(0), numberOfExploredStates(0), ecDetections(0), failedEcDetections(0), totalNumberOfEcDetected(0) { - // Intentionally left empty. - } - - std::size_t pathsSampled; - std::size_t explorationSteps; - std::size_t maxPathLength; - std::size_t numberOfTargetStates; - std::size_t numberOfExploredStates; - std::size_t ecDetections; - std::size_t failedEcDetections; - std::size_t totalNumberOfEcDetected; - }; - - // A struct containing the data required for state exploration. - struct StateGeneration { - StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, storm::expressions::Expression const& targetStateExpression) : generator(program, variableInformation, false), targetStateExpression(targetStateExpression) { - // Intentionally left empty. - } - - std::vector getInitialStates() { - return generator.getInitialStates(stateToIdCallback); - } - - storm::generator::StateBehavior expand() { - return generator.expand(stateToIdCallback); - } - - bool isTargetState() const { - return generator.satisfies(targetStateExpression); - } - - storm::generator::PrismNextStateGenerator generator; - std::function stateToIdCallback; - storm::expressions::Expression targetStateExpression; - }; - // A structure containing the data assembled during exploration. struct ExplorationInformation { - ExplorationInformation(uint_fast64_t bitsPerBucket, bool localPrecomputation, uint_fast64_t numberOfExplorationStepsUntilPrecomputation, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localPrecomputation(localPrecomputation), numberOfExplorationStepsUntilPrecomputation(numberOfExplorationStepsUntilPrecomputation) { - // Intentionally left empty. + ExplorationInformation(uint_fast64_t bitsPerBucket, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localPrecomputation(false), numberOfExplorationStepsUntilPrecomputation(100000), numberOfSampledPathsUntilPrecomputation(), nextStateHeuristic(storm::settings::modules::LearningSettings::NextStateHeuristic::DifferenceWeightedProbability) { + + storm::settings::modules::LearningSettings const& settings = storm::settings::learningSettings(); + localPrecomputation = settings.isLocalPrecomputationSet(); + if (settings.isNumberOfSampledPathsUntilPrecomputationSet()) { + numberOfSampledPathsUntilPrecomputation = settings.getNumberOfSampledPathsUntilPrecomputation(); + } + + nextStateHeuristic = settings.getNextStateHeuristic(); + STORM_LOG_ASSERT(useDifferenceWeightedProbabilityHeuristic() || useProbabilityHeuristic(), "Illegal next-state heuristic."); } storm::storage::sparse::StateStorage stateStorage; @@ -108,6 +81,9 @@ namespace storm { bool localPrecomputation; uint_fast64_t numberOfExplorationStepsUntilPrecomputation; + boost::optional numberOfSampledPathsUntilPrecomputation; + + storm::settings::modules::LearningSettings::NextStateHeuristic nextStateHeuristic; void setInitialStates(std::vector const& initialStates) { stateStorage.initialStateIndices = initialStates; @@ -208,22 +184,111 @@ namespace storm { return !maximize(); } - bool performPrecomputation(std::size_t& performedExplorationSteps) const { - bool result = performedExplorationSteps > numberOfExplorationStepsUntilPrecomputation; + bool performPrecomputationExcessiveExplorationSteps(std::size_t& numberExplorationStepsSinceLastPrecomputation) const { + bool result = numberExplorationStepsSinceLastPrecomputation > numberOfExplorationStepsUntilPrecomputation; if (result) { - std::cout << "triggering precomp" << std::endl; - performedExplorationSteps = 0; + numberExplorationStepsSinceLastPrecomputation = 0; } return result; } + bool performPrecomputationExcessiveSampledPaths(std::size_t& numberOfSampledPathsSinceLastPrecomputation) const { + if (!numberOfSampledPathsUntilPrecomputation) { + return false; + } else { + bool result = numberOfSampledPathsSinceLastPrecomputation > numberOfSampledPathsUntilPrecomputation.get(); + if (result) { + numberOfSampledPathsSinceLastPrecomputation = 0; + } + return result; + } + } + bool useLocalPrecomputation() const { return localPrecomputation; } - + bool useGlobalPrecomputation() const { return !useLocalPrecomputation(); } + + storm::settings::modules::LearningSettings::NextStateHeuristic const& getNextStateHeuristic() const { + return nextStateHeuristic; + } + + bool useDifferenceWeightedProbabilityHeuristic() const { + return nextStateHeuristic == storm::settings::modules::LearningSettings::NextStateHeuristic::DifferenceWeightedProbability; + } + + bool useProbabilityHeuristic() const { + return nextStateHeuristic == storm::settings::modules::LearningSettings::NextStateHeuristic::Probability; + } + }; + + // A struct that keeps track of certain statistics during the computation. + struct Statistics { + 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. + } + + void explorationStep() { + ++explorationSteps; + ++explorationStepsSinceLastPrecomputation; + } + + void sampledPath() { + ++pathsSampled; + ++pathsSampledSinceLastPrecomputation; + } + + void updateMaxPathLength(std::size_t const& currentPathLength) { + maxPathLength = std::max(maxPathLength, currentPathLength); + } + + 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; + + void printToStream(std::ostream& out, ExplorationInformation const& explorationInformation) const { + out << std::endl << "Learning 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; + } + }; + + // A struct containing the data required for state exploration. + struct StateGeneration { + StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, storm::expressions::Expression const& targetStateExpression) : generator(program, variableInformation, false), targetStateExpression(targetStateExpression) { + // Intentionally left empty. + } + + std::vector getInitialStates() { + return generator.getInitialStates(stateToIdCallback); + } + + storm::generator::StateBehavior expand() { + return generator.expand(stateToIdCallback); + } + + bool isTargetState() const { + return generator.satisfies(targetStateExpression); + } + + storm::generator::PrismNextStateGenerator generator; + std::function stateToIdCallback; + storm::expressions::Expression targetStateExpression; }; // A struct containg the lower and upper bounds per state and action. diff --git a/src/settings/modules/LearningSettings.cpp b/src/settings/modules/LearningSettings.cpp index fdddd1672..0ce44a8e4 100644 --- a/src/settings/modules/LearningSettings.cpp +++ b/src/settings/modules/LearningSettings.cpp @@ -13,11 +13,17 @@ namespace storm { const std::string LearningSettings::moduleName = "learning"; const std::string LearningSettings::precomputationTypeOptionName = "precomp"; const std::string LearningSettings::numberOfExplorationStepsUntilPrecomputationOptionName = "stepsprecomp"; + const std::string LearningSettings::numberOfSampledPathsUntilPrecomputationOptionName = "pathsprecomp"; + const std::string LearningSettings::nextStateHeuristicOptionName = "nextstate"; LearningSettings::LearningSettings(storm::settings::SettingsManager& settingsManager) : ModuleSettings(settingsManager, moduleName) { std::vector types = { "local", "global" }; - this->addOption(storm::settings::OptionBuilder(moduleName, precomputationTypeOptionName, true, "Sets the kind of precomputation used. Available are: { local, global }.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(types)).setDefaultValueString("local").build()).build()); - this->addOption(storm::settings::OptionBuilder(moduleName, numberOfExplorationStepsUntilPrecomputationOptionName, false, "Sets the number of exploration steps until a precomputation is triggered.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of exploration steps to perform.").setDefaultValueUnsignedInteger(100000).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, precomputationTypeOptionName, true, "Sets the kind of precomputation used. Available are: { local, global }.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(types)).setDefaultValueString("global").build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, numberOfExplorationStepsUntilPrecomputationOptionName, false, "Sets the number of exploration steps to perform until a precomputation is triggered.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of exploration steps to perform.").setDefaultValueUnsignedInteger(100000).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, numberOfSampledPathsUntilPrecomputationOptionName, false, "If set, a precomputation is perfomed periodically after the given number of paths has been sampled.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of paths to sample until a precomputation is triggered.").setDefaultValueUnsignedInteger(100000).build()).build()); + + std::vector nextStateHeuristics = { "probdiff", "prob" }; + this->addOption(storm::settings::OptionBuilder(moduleName, nextStateHeuristicOptionName, true, "Sets the next-state heuristic to use. Available are: { probdiff, prob } where 'prob' samples according to the probabilities in the system and 'probdiff' weights the probabilities with the differences between the current bounds.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the heuristic to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(nextStateHeuristics)).setDefaultValueString("probdiff").build()).build()); } bool LearningSettings::isLocalPrecomputationSet() const { @@ -41,15 +47,36 @@ namespace storm { } else if (typeAsString == "global") { return LearningSettings::PrecomputationType::Global; } - STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown EC-detection type '" << typeAsString << "'."); + STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown precomputation type '" << typeAsString << "'."); } uint_fast64_t LearningSettings::getNumberOfExplorationStepsUntilPrecomputation() const { return this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getArgumentByName("count").getValueAsUnsignedInteger(); } + bool LearningSettings::isNumberOfSampledPathsUntilPrecomputationSet() const { + return this->getOption(numberOfSampledPathsUntilPrecomputationOptionName).getHasOptionBeenSet(); + } + + uint_fast64_t LearningSettings::getNumberOfSampledPathsUntilPrecomputation() const { + return this->getOption(numberOfSampledPathsUntilPrecomputationOptionName).getArgumentByName("count").getValueAsUnsignedInteger(); + } + + LearningSettings::NextStateHeuristic LearningSettings::getNextStateHeuristic() const { + std::string nextStateHeuristicAsString = this->getOption(nextStateHeuristicOptionName).getArgumentByName("name").getValueAsString(); + if (nextStateHeuristicAsString == "probdiff") { + return LearningSettings::NextStateHeuristic::DifferenceWeightedProbability; + } else if (nextStateHeuristicAsString == "prob") { + return LearningSettings::NextStateHeuristic::Probability; + } + STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown next-state heuristic '" << nextStateHeuristicAsString << "'."); + } + bool LearningSettings::check() const { - bool optionsSet = this->getOption(precomputationTypeOptionName).getHasOptionBeenSet() || this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getHasOptionBeenSet(); + bool optionsSet = this->getOption(precomputationTypeOptionName).getHasOptionBeenSet() || + this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getHasOptionBeenSet() || + this->getOption(numberOfSampledPathsUntilPrecomputationOptionName).getHasOptionBeenSet() || + this->getOption(nextStateHeuristicOptionName).getHasOptionBeenSet(); STORM_LOG_WARN_COND(storm::settings::generalSettings().getEngine() == storm::settings::modules::GeneralSettings::Engine::Learning || !optionsSet, "Learning engine is not selected, so setting options for it has no effect."); return true; } diff --git a/src/settings/modules/LearningSettings.h b/src/settings/modules/LearningSettings.h index 8db1fe44a..2aa0d61a7 100644 --- a/src/settings/modules/LearningSettings.h +++ b/src/settings/modules/LearningSettings.h @@ -15,6 +15,9 @@ namespace storm { // An enumeration of all available precomputation types. enum class PrecomputationType { Local, Global }; + // The available heuristics to choose the next state. + enum class NextStateHeuristic { DifferenceWeightedProbability, Probability }; + /*! * Creates a new set of learning settings that is managed by the given manager. * @@ -49,6 +52,27 @@ namespace storm { * @return The number of exploration steps to perform until a precomputation is triggered. */ uint_fast64_t getNumberOfExplorationStepsUntilPrecomputation() const; + + /* + * Retrieves whether the option to perform a precomputation after a given number of sampled paths was set. + * + * @return True iff a precomputation after a given number of sampled paths is to be performed. + */ + bool isNumberOfSampledPathsUntilPrecomputationSet() const; + + /*! + * Retrieves the number of paths to sample until a precomputation is triggered. + * + * @return The the number of paths to sample until a precomputation is triggered. + */ + uint_fast64_t getNumberOfSampledPathsUntilPrecomputation() const; + + /*! + * Retrieves the selected next-state heuristic. + * + * @return The selected next-state heuristic. + */ + NextStateHeuristic getNextStateHeuristic() const; virtual bool check() const override; @@ -59,6 +83,8 @@ namespace storm { // Define the string names of the options as constants. static const std::string precomputationTypeOptionName; static const std::string numberOfExplorationStepsUntilPrecomputationOptionName; + static const std::string numberOfSampledPathsUntilPrecomputationOptionName; + static const std::string nextStateHeuristicOptionName; }; } // namespace modules } // namespace settings From 40b8892f7f7771d58e7629d3b709a4add69b155d Mon Sep 17 00:00:00 2001 From: dehnert Date: Mon, 11 Apr 2016 15:38:41 +0200 Subject: [PATCH 24/31] removed debug output Former-commit-id: bd55256a50304f19b1808b8c38c63ec10bdb9623 --- .../reachability/SparseMdpLearningModelChecker.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index f526ea27c..b0f6cc4da 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -373,14 +373,6 @@ namespace storm { // 3. use MEC decomposition to collapse MECs. STORM_LOG_TRACE("Starting " << (explorationInformation.useLocalPrecomputation() ? "local" : "global") << " precomputation."); - for (int row = 0; row < explorationInformation.matrix.size(); ++row) { - std::cout << "row " << row << ":"; - for (auto const& el : explorationInformation.matrix[row]) { - std::cout << el.getColumn() << ", " << el.getValue() << " [" << bounds.getLowerBoundForState(el.getColumn(), explorationInformation) << ", " << bounds.getUpperBoundForState(el.getColumn(), explorationInformation) << "]" << " "; - } - std::cout << std::endl; - } - // Construct the matrix that represents the fragment of the system contained in the currently sampled path. storm::storage::SparseMatrixBuilder builder(0, 0, 0, false, true, 0); @@ -413,7 +405,6 @@ namespace storm { for (StateType index = 0; index < relevantStates.size(); ++index) { relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); if (storm::utility::isOne(bounds.getLowerBoundForState(relevantStates[index], explorationInformation))) { - std::cout << "target states: " << targetStates << std::endl; targetStates.set(index); } } @@ -492,9 +483,6 @@ namespace storm { statesWithProbability1 = storm::utility::graph::performProb1A(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); } - std::cout << "prob0: " << statesWithProbability0 << std::endl; - std::cout << "prob1: " << statesWithProbability1 << std::endl; - // Set the bounds of the identified states. for (auto state : statesWithProbability0) { StateType originalState = relevantStates[state]; From 1424d536cabc9d757bcded4b871837605ef1047c Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 12 Apr 2016 18:15:30 +0200 Subject: [PATCH 25/31] renamed learning to exploration engine and started on a minor refactoring Former-commit-id: 0fa973dfe5fea5489b251e8e584387eee976c0ec --- src/CMakeLists.txt | 2 + src/cli/entrypoints.h | 28 +-- src/generator/PrismNextStateGenerator.cpp | 3 + .../{CopyVisitor.cpp => CloneVisitor.cpp} | 0 src/logic/Formula.cpp | 8 +- src/logic/Formula.h | 9 +- src/logic/FragmentChecker.cpp | 9 +- src/logic/FragmentSpecification.cpp | 29 +++ src/logic/FragmentSpecification.h | 10 ++ src/logic/ToExpressionVisitor.cpp | 16 +- src/logic/ToExpressionVisitor.h | 2 +- src/modelchecker/exploration/BoundValues.cpp | 0 src/modelchecker/exploration/BoundValues.h | 137 ++++++++++++++ .../exploration/ExplorationInformation.cpp | 0 .../exploration/ExplorationInformation.h | 0 .../SparseMdpExplorationModelChecker.cpp} | 83 ++++----- .../SparseMdpExplorationModelChecker.h} | 170 +++--------------- .../exploration/StateGeneration.cpp | 0 .../exploration/StateGeneration.h | 0 src/settings/SettingsManager.cpp | 8 +- src/settings/SettingsManager.h | 8 +- ...ngSettings.cpp => ExplorationSettings.cpp} | 40 ++--- ...arningSettings.h => ExplorationSettings.h} | 14 +- src/settings/modules/GeneralSettings.cpp | 8 +- src/settings/modules/GeneralSettings.h | 2 +- src/utility/storm.h | 2 +- src/utility/vector.h | 2 - 27 files changed, 332 insertions(+), 258 deletions(-) rename src/logic/{CopyVisitor.cpp => CloneVisitor.cpp} (100%) create mode 100644 src/modelchecker/exploration/BoundValues.cpp create mode 100644 src/modelchecker/exploration/BoundValues.h create mode 100644 src/modelchecker/exploration/ExplorationInformation.cpp create mode 100644 src/modelchecker/exploration/ExplorationInformation.h rename src/modelchecker/{reachability/SparseMdpLearningModelChecker.cpp => exploration/SparseMdpExplorationModelChecker.cpp} (86%) rename src/modelchecker/{reachability/SparseMdpLearningModelChecker.h => exploration/SparseMdpExplorationModelChecker.h} (64%) create mode 100644 src/modelchecker/exploration/StateGeneration.cpp create mode 100644 src/modelchecker/exploration/StateGeneration.h rename src/settings/modules/{LearningSettings.cpp => ExplorationSettings.cpp} (72%) rename src/settings/modules/{LearningSettings.h => ExplorationSettings.h} (88%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7587a3f22..345efa36c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ file(GLOB_RECURSE STORM_MODELCHECKER_PRCTL_HELPER_FILES ${PROJECT_SOURCE_DIR}/sr file(GLOB STORM_MODELCHECKER_CSL_FILES ${PROJECT_SOURCE_DIR}/src/modelchecker/csl/*.h ${PROJECT_SOURCE_DIR}/src/modelchecker/csl/*.cpp) file(GLOB_RECURSE STORM_MODELCHECKER_CSL_HELPER_FILES ${PROJECT_SOURCE_DIR}/src/modelchecker/csl/helper/*.h ${PROJECT_SOURCE_DIR}/src/modelchecker/csl/helper/*.cpp) file(GLOB_RECURSE STORM_MODELCHECKER_REACHABILITY_FILES ${PROJECT_SOURCE_DIR}/src/modelchecker/reachability/*.h ${PROJECT_SOURCE_DIR}/src/modelchecker/reachability/*.cpp) +file(GLOB_RECURSE STORM_MODELCHECKER_EXPLORATION_FILES ${PROJECT_SOURCE_DIR}/src/modelchecker/exploration/*.h ${PROJECT_SOURCE_DIR}/src/modelchecker/exploration/*.cpp) file(GLOB_RECURSE STORM_MODELCHECKER_PROPOSITIONAL_FILES ${PROJECT_SOURCE_DIR}/src/modelchecker/propositional/*.h ${PROJECT_SOURCE_DIR}/src/modelchecker/propositional/*.cpp) file(GLOB_RECURSE STORM_MODELCHECKER_RESULTS_FILES ${PROJECT_SOURCE_DIR}/src/modelchecker/results/*.h ${PROJECT_SOURCE_DIR}/src/modelchecker/results/*.cpp) file(GLOB_RECURSE STORM_COUNTEREXAMPLES_FILES ${PROJECT_SOURCE_DIR}/src/counterexamples/*.h ${PROJECT_SOURCE_DIR}/src/counterexamples/*.cpp) @@ -68,6 +69,7 @@ source_group(modelchecker\\prctl FILES ${STORM_MODELCHECKER_PRCTL_FILES}) source_group(modelchecker\\prctl\\helper FILES ${STORM_MODELCHECKER_PRCTL_HELPER_FILES}) source_group(modelchecker\\csl FILES ${STORM_MODELCHECKER_CSL_FILES}) source_group(modelchecker\\csl\\helper FILES ${STORM_MODELCHECKER_CSL_HELPER_FILES}) +source_group(modelchecker\\exploration FILES ${STORM_MODELCHECKER_EXPLORATION_FILES}) source_group(modelchecker\\reachability FILES ${STORM_MODELCHECKER_REACHABILITY_FILES}) source_group(modelchecker\\propositional FILES ${STORM_MODELCHECKER_PROPOSITIONAL_FILES}) source_group(modelchecker\\results FILES ${STORM_MODELCHECKER_RESULTS_FILES}) diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index 505ec0cd3..17291f750 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -55,33 +55,33 @@ namespace storm { } template - void verifySymbolicModelWithLearningEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { + void verifySymbolicModelWithExplorationEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant = false) { for (auto const& formula : formulas) { - STORM_LOG_THROW(program.getModelType() == storm::prism::Program::ModelType::DTMC || program.getModelType() == storm::prism::Program::ModelType::MDP, storm::exceptions::InvalidSettingsException, "Currently learning-based verification is only available for DTMCs and MDPs."); + STORM_LOG_THROW(program.getModelType() == storm::prism::Program::ModelType::DTMC || program.getModelType() == storm::prism::Program::ModelType::MDP, storm::exceptions::InvalidSettingsException, "Currently exploration-based verification is only available for DTMCs and MDPs."); std::cout << std::endl << "Model checking property: " << *formula << " ..."; storm::modelchecker::CheckTask task(*formula, onlyInitialStatesRelevant); - storm::modelchecker::SparseMdpLearningModelChecker checker(program, storm::utility::prism::parseConstantDefinitionString(program, storm::settings::generalSettings().getConstantDefinitionString())); + storm::modelchecker::SparseMdpExplorationModelChecker checker(program, storm::utility::prism::parseConstantDefinitionString(program, storm::settings::generalSettings().getConstantDefinitionString())); std::unique_ptr result; if (checker.canHandle(task)) { result = checker.check(task); + if (result) { + std::cout << " done." << std::endl; + std::cout << "Result (initial states): "; + std::cout << *result << std::endl; + } else { + std::cout << " skipped, because the modelling formalism is currently unsupported." << std::endl; + } } else { std::cout << " skipped, because the formula cannot be handled by the selected engine/method." << std::endl; } - if (result) { - std::cout << " done." << std::endl; - std::cout << "Result (initial states): "; - std::cout << *result << std::endl; - } else { - std::cout << " skipped, because the modelling formalism is currently unsupported." << std::endl; - } } } #ifdef STORM_HAVE_CARL template<> - void verifySymbolicModelWithLearningEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant) { - STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Learning-based verification does currently not support parametric models."); + void verifySymbolicModelWithExplorationEngine(storm::prism::Program const& program, std::vector> const& formulas, bool onlyInitialStatesRelevant) { + STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Exploration-based verification does currently not support parametric models."); } #endif @@ -151,8 +151,8 @@ namespace storm { if (settings.getEngine() == storm::settings::modules::GeneralSettings::Engine::AbstractionRefinement) { verifySymbolicModelWithAbstractionRefinementEngine(program, formulas, onlyInitialStatesRelevant); - } else if (settings.getEngine() == storm::settings::modules::GeneralSettings::Engine::Learning) { - verifySymbolicModelWithLearningEngine(program, formulas, onlyInitialStatesRelevant); + } else if (settings.getEngine() == storm::settings::modules::GeneralSettings::Engine::Exploration) { + verifySymbolicModelWithExplorationEngine(program, formulas, onlyInitialStatesRelevant); } else { storm::storage::ModelFormulasPair modelFormulasPair = buildSymbolicModel(program, formulas); STORM_LOG_THROW(modelFormulasPair.model != nullptr, storm::exceptions::InvalidStateException, diff --git a/src/generator/PrismNextStateGenerator.cpp b/src/generator/PrismNextStateGenerator.cpp index 0064636b6..3ba8b89f7 100644 --- a/src/generator/PrismNextStateGenerator.cpp +++ b/src/generator/PrismNextStateGenerator.cpp @@ -59,6 +59,9 @@ namespace storm { template bool PrismNextStateGenerator::satisfies(storm::expressions::Expression const& expression) const { + if (expression.isTrue()) { + return true; + } return evaluator.asBool(expression); } diff --git a/src/logic/CopyVisitor.cpp b/src/logic/CloneVisitor.cpp similarity index 100% rename from src/logic/CopyVisitor.cpp rename to src/logic/CloneVisitor.cpp diff --git a/src/logic/Formula.cpp b/src/logic/Formula.cpp index 492230a33..de915eaa1 100644 --- a/src/logic/Formula.cpp +++ b/src/logic/Formula.cpp @@ -419,9 +419,13 @@ namespace storm { return visitor.substitute(*this); } - storm::expressions::Expression Formula::toExpression() const { + storm::expressions::Expression Formula::toExpression(storm::expressions::ExpressionManager const& manager, std::map const& labelToExpressionMapping) const { ToExpressionVisitor visitor; - return visitor.toExpression(*this); + if (labelToExpressionMapping.empty()) { + return visitor.toExpression(*this, manager); + } else { + return visitor.toExpression(*this->substitute(labelToExpressionMapping), manager); + } } std::shared_ptr Formula::asSharedPointer() { diff --git a/src/logic/Formula.h b/src/logic/Formula.h index 11865b950..d5625a296 100644 --- a/src/logic/Formula.h +++ b/src/logic/Formula.h @@ -190,12 +190,15 @@ namespace storm { std::shared_ptr substitute(std::map const& labelSubstitution) const; /*! - * Takes the formula and converts it to an equivalent expression assuming that only atomic expression formulas - * and boolean connectives appear in the formula. + * Takes the formula and converts it to an equivalent expression. The formula may contain atomic labels, but + * then the given mapping must provide a corresponding expression. Other than that, only atomic expression + * formulas and boolean connectives may appear in the formula. * + * @param manager The manager responsible for the expressions in the formula and the resulting expression. + * @param A mapping from labels to the expressions that define them. * @return An equivalent expression. */ - storm::expressions::Expression toExpression() const; + storm::expressions::Expression toExpression(storm::expressions::ExpressionManager const& manager, std::map const& labelToExpressionMapping = {}) const; std::string toString() const; virtual std::ostream& writeToStream(std::ostream& out) const = 0; diff --git a/src/logic/FragmentChecker.cpp b/src/logic/FragmentChecker.cpp index 09bfc31dd..b86a96693 100644 --- a/src/logic/FragmentChecker.cpp +++ b/src/logic/FragmentChecker.cpp @@ -19,8 +19,13 @@ namespace storm { }; bool FragmentChecker::conformsToSpecification(Formula const& f, FragmentSpecification const& specification) const { - boost::any result = f.accept(*this, InheritedInformation(specification)); - return boost::any_cast(result); + bool result = boost::any_cast(f.accept(*this, InheritedInformation(specification))); + + if (specification.isOperatorAtTopLevelRequired()) { + result &= f.isOperatorFormula(); + } + + return result; } boost::any FragmentChecker::visit(AtomicExpressionFormula const& f, boost::any const& data) const { diff --git a/src/logic/FragmentSpecification.cpp b/src/logic/FragmentSpecification.cpp index 31f2eeee2..6877496d2 100644 --- a/src/logic/FragmentSpecification.cpp +++ b/src/logic/FragmentSpecification.cpp @@ -17,6 +17,18 @@ namespace storm { return propositional; } + FragmentSpecification reachability() { + FragmentSpecification reachability = propositional(); + + reachability.setProbabilityOperatorsAllowed(true); + reachability.setUntilFormulasAllowed(true); + reachability.setReachabilityProbabilityFormulasAllowed(true); + reachability.setOperatorAtTopLevelRequired(true); + reachability.setNestedOperatorsAllowed(false); + + return reachability; + } + FragmentSpecification pctl() { FragmentSpecification pctl = propositional(); @@ -31,6 +43,14 @@ namespace storm { return pctl; } + FragmentSpecification flatPctl() { + FragmentSpecification flatPctl = pctl(); + + flatPctl.setNestedOperatorsAllowed(false); + + return flatPctl; + } + FragmentSpecification prctl() { FragmentSpecification prctl = pctl(); @@ -100,6 +120,8 @@ namespace storm { qualitativeOperatorResults = true; quantitativeOperatorResults = true; + + operatorAtTopLevelRequired = false; } FragmentSpecification FragmentSpecification::copy() const { @@ -386,6 +408,13 @@ namespace storm { return *this; } + bool FragmentSpecification::isOperatorAtTopLevelRequired() const { + return operatorAtTopLevelRequired; + } + + FragmentSpecification& FragmentSpecification::setOperatorAtTopLevelRequired(bool newValue) { + operatorAtTopLevelRequired = newValue; + } } } \ No newline at end of file diff --git a/src/logic/FragmentSpecification.h b/src/logic/FragmentSpecification.h index 5074187b8..6637d0750 100644 --- a/src/logic/FragmentSpecification.h +++ b/src/logic/FragmentSpecification.h @@ -100,6 +100,9 @@ namespace storm { bool areQualitativeOperatorResultsAllowed() const; FragmentSpecification& setQualitativeOperatorResultsAllowed(bool newValue); + bool isOperatorAtTopLevelRequired() const; + FragmentSpecification& setOperatorAtTopLevelRequired(bool newValue); + FragmentSpecification& setOperatorsAllowed(bool newValue); FragmentSpecification& setTimeAllowed(bool newValue); FragmentSpecification& setLongRunAverageProbabilitiesAllowed(bool newValue); @@ -142,13 +145,20 @@ namespace storm { bool varianceAsMeasureType; bool quantitativeOperatorResults; bool qualitativeOperatorResults; + bool operatorAtTopLevelRequired; }; // Propositional. FragmentSpecification propositional(); + // Just reachability properties. + FragmentSpecification reachability(); + // Regular PCTL. FragmentSpecification pctl(); + + // Flat PCTL. + FragmentSpecification flatPctl(); // PCTL + cumulative, instantaneous, reachability and long-run rewards. FragmentSpecification prctl(); diff --git a/src/logic/ToExpressionVisitor.cpp b/src/logic/ToExpressionVisitor.cpp index 887f9d405..53dfedeca 100644 --- a/src/logic/ToExpressionVisitor.cpp +++ b/src/logic/ToExpressionVisitor.cpp @@ -2,14 +2,16 @@ #include "src/logic/Formulas.h" +#include "src/storage/expressions/ExpressionManager.h" + #include "src/utility/macros.h" #include "src/exceptions/InvalidOperationException.h" namespace storm { namespace logic { - storm::expressions::Expression ToExpressionVisitor::toExpression(Formula const& f) const { - boost::any result = f.accept(*this, boost::any()); + storm::expressions::Expression ToExpressionVisitor::toExpression(Formula const& f, storm::expressions::ExpressionManager const& manager) const { + boost::any result = f.accept(*this, std::ref(manager)); return boost::any_cast(result); } @@ -18,7 +20,7 @@ namespace storm { } boost::any ToExpressionVisitor::visit(AtomicLabelFormula const& f, boost::any const& data) const { - STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression from formula that contains illegal elements."); + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot assemble expression, because the undefined atomic label '" << f.getLabel() << "' appears in the formula."); } boost::any ToExpressionVisitor::visit(BinaryBooleanStateFormula const& f, boost::any const& data) const { @@ -35,7 +37,13 @@ namespace storm { } boost::any ToExpressionVisitor::visit(BooleanLiteralFormula const& f, boost::any const& data) const { - return std::static_pointer_cast(std::make_shared(f)); + storm::expressions::Expression result; + if (f.isTrueFormula()) { + result = boost::any_cast>(data).get().boolean(true); + } else { + result = boost::any_cast>(data).get().boolean(false); + } + return result; } boost::any ToExpressionVisitor::visit(BoundedUntilFormula const& f, boost::any const& data) const { diff --git a/src/logic/ToExpressionVisitor.h b/src/logic/ToExpressionVisitor.h index a42258a46..ee4a81d8f 100644 --- a/src/logic/ToExpressionVisitor.h +++ b/src/logic/ToExpressionVisitor.h @@ -10,7 +10,7 @@ namespace storm { class ToExpressionVisitor : public FormulaVisitor { public: - storm::expressions::Expression toExpression(Formula const& f) const; + storm::expressions::Expression toExpression(Formula const& f, storm::expressions::ExpressionManager const& manager) const; virtual boost::any visit(AtomicExpressionFormula const& f, boost::any const& data) const override; virtual boost::any visit(AtomicLabelFormula const& f, boost::any const& data) const override; diff --git a/src/modelchecker/exploration/BoundValues.cpp b/src/modelchecker/exploration/BoundValues.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/modelchecker/exploration/BoundValues.h b/src/modelchecker/exploration/BoundValues.h new file mode 100644 index 000000000..3640fa9c6 --- /dev/null +++ b/src/modelchecker/exploration/BoundValues.h @@ -0,0 +1,137 @@ +#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 lowerBoundsPerState; + std::vector upperBoundsPerState; + std::vector lowerBoundsPerAction; + std::vector upperBoundsPerAction; + + std::pair getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { + ActionType index = explorationInformation.getRowGroup(state); + if (index == explorationInformation.getUnexploredMarker()) { + return std::make_pair(storm::utility::zero(), storm::utility::one()); + } 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(); + } 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(); + } else { + return getUpperBoundForRowGroup(index); + } + } + + ValueType const& getUpperBoundForRowGroup(StateType const& rowGroup) const { + return upperBoundsPerState[rowGroup]; + } + + std::pair 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 bounds = getBoundsForState(state, explorationInformation); + return bounds.second - bounds.first; + } + + void initializeBoundsForNextState(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { + lowerBoundsPerState.push_back(vals.first); + upperBoundsPerState.push_back(vals.second); + } + + void initializeBoundsForNextAction(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { + 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 const& values) { + lowerBoundsPerAction[action] = values.first; + upperBoundsPerAction[action] = values.second; + } + + void setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { + StateType const& rowGroup = explorationInformation.getRowGroup(state); + setBoundsForRowGroup(rowGroup, values); + } + + void setBoundsForRowGroup(StateType const& rowGroup, std::pair 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; + } + }; + + } + } +} \ No newline at end of file diff --git a/src/modelchecker/exploration/ExplorationInformation.cpp b/src/modelchecker/exploration/ExplorationInformation.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/modelchecker/exploration/ExplorationInformation.h b/src/modelchecker/exploration/ExplorationInformation.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp similarity index 86% rename from src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp rename to src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp index b0f6cc4da..037d97800 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp @@ -1,4 +1,4 @@ -#include "src/modelchecker/reachability/SparseMdpLearningModelChecker.h" +#include "src/modelchecker/exploration/SparseMdpExplorationModelChecker.h" #include "src/storage/SparseMatrix.h" #include "src/storage/MaximalEndComponentDecomposition.h" @@ -22,27 +22,30 @@ namespace storm { namespace modelchecker { + template - SparseMdpLearningModelChecker::SparseMdpLearningModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions) : program(storm::utility::prism::preprocessProgram(program, constantDefinitions)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator() { + SparseMdpExplorationModelChecker::SparseMdpExplorationModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions) : program(storm::utility::prism::preprocessProgram(program, constantDefinitions)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator() { // Intentionally left empty. } template - bool SparseMdpLearningModelChecker::canHandle(CheckTask const& checkTask) const { + bool SparseMdpExplorationModelChecker::canHandle(CheckTask const& checkTask) const { storm::logic::Formula const& formula = checkTask.getFormula(); - storm::logic::FragmentSpecification fragment = storm::logic::propositional().setProbabilityOperatorsAllowed(true).setReachabilityProbabilityFormulasAllowed(true).setNestedOperatorsAllowed(false); + storm::logic::FragmentSpecification fragment = storm::logic::reachability(); return formula.isInFragment(fragment) && checkTask.isOnlyInitialStatesRelevantSet(); } template - std::unique_ptr SparseMdpLearningModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { - storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); - storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); + std::unique_ptr SparseMdpExplorationModelChecker::computeUntilProbabilities(CheckTask const& checkTask) { + storm::logic::UntilFormula const& untilFormula = checkTask.getFormula(); + storm::logic::Formula const& conditionFormula = untilFormula.getLeftSubformula(); + storm::logic::Formula const& targetFormula = untilFormula.getRightSubformula(); STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "For nondeterministic systems, an optimization direction (min/max) must be given in the property."); - StateGeneration stateGeneration(program, variableInformation, getTargetStateExpression(subformula)); + std::map labelToExpressionMapping = program.getLabelToExpressionMapping(); + StateGeneration stateGeneration(program, variableInformation, conditionFormula.toExpression(program.getManager(), labelToExpressionMapping), targetFormula.toExpression(program.getManager(), labelToExpressionMapping)); - ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::learningSettings().getNumberOfExplorationStepsUntilPrecomputation()); + ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::explorationSettings().getNumberOfExplorationStepsUntilPrecomputation()); explorationInformation.optimizationDirection = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; // The first row group starts at action 0. @@ -52,24 +55,12 @@ namespace storm { stateGeneration.stateToIdCallback = createStateToIdCallback(explorationInformation); // Compute and return result. - std::tuple boundsForInitialState = performLearningProcedure(stateGeneration, explorationInformation); + std::tuple boundsForInitialState = performExploration(stateGeneration, explorationInformation); return std::make_unique>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); } template - storm::expressions::Expression SparseMdpLearningModelChecker::getTargetStateExpression(storm::logic::Formula const& subformula) const { - std::shared_ptr preparedSubformula = subformula.substitute(program.getLabelToExpressionMapping()); - storm::expressions::Expression result; - try { - result = preparedSubformula->toExpression(); - } catch(storm::exceptions::InvalidOperationException const& e) { - STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "The property refers to unknown labels."); - } - return result; - } - - template - std::function::StateType (storm::generator::CompressedState const&)> SparseMdpLearningModelChecker::createStateToIdCallback(ExplorationInformation& explorationInformation) const { + std::function::StateType (storm::generator::CompressedState const&)> SparseMdpExplorationModelChecker::createStateToIdCallback(ExplorationInformation& explorationInformation) const { return [&explorationInformation,this] (storm::generator::CompressedState const& state) -> StateType { StateType newIndex = explorationInformation.stateStorage.numberOfStates; @@ -85,11 +76,11 @@ namespace storm { } template - std::tuple::StateType, ValueType, ValueType> SparseMdpLearningModelChecker::performLearningProcedure(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { + std::tuple::StateType, ValueType, ValueType> SparseMdpExplorationModelChecker::performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { // Generate the initial state so we know where to start the simulation. explorationInformation.setInitialStates(stateGeneration.getInitialStates()); - STORM_LOG_THROW(explorationInformation.getNumberOfInitialStates() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); + STORM_LOG_THROW(explorationInformation.getNumberOfInitialStates() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the exploration engine."); StateType initialStateIndex = explorationInformation.getFirstInitialState(); // Create a structure that holds the bounds for the states and actions. @@ -139,7 +130,7 @@ namespace storm { } template - bool SparseMdpLearningModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const { + bool SparseMdpExplorationModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const { // Start the search from the initial state. stack.push_back(std::make_pair(explorationInformation.getFirstInitialState(), 0)); @@ -201,7 +192,7 @@ namespace storm { } template - bool SparseMdpLearningModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { + bool SparseMdpExplorationModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { bool isTerminalState = false; bool isTargetState = false; @@ -217,12 +208,12 @@ namespace storm { // Before generating the behavior of the state, we need to determine whether it's a target state that // does not need to be expanded. - stateGeneration.generator.load(currentState); + stateGeneration.load(currentState); if (stateGeneration.isTargetState()) { ++stats.numberOfTargetStates; isTargetState = true; isTerminalState = true; - } else { + } else if (stateGeneration.isConditionState()) { STORM_LOG_TRACE("Exploring state."); // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. @@ -273,6 +264,10 @@ namespace storm { bounds.setBoundsForState(currentStateId, explorationInformation, stateBounds); STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << bounds.getLowerBoundForState(currentStateId, explorationInformation) << " and " << bounds.getUpperBoundForState(currentStateId, explorationInformation) << "."); } + } else { + // In this case, the state is neither a target state nor a condition state and therefore a rejecting + // terminal state. + isTerminalState = true; } if (isTerminalState) { @@ -298,7 +293,7 @@ namespace storm { } template - typename SparseMdpLearningModelChecker::ActionType SparseMdpLearningModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + typename SparseMdpExplorationModelChecker::ActionType SparseMdpExplorationModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { // Determine the values of all available actions. std::vector> actionValues; StateType rowGroup = explorationInformation.getRowGroup(currentStateId); @@ -336,7 +331,7 @@ namespace storm { } template - typename SparseMdpLearningModelChecker::StateType SparseMdpLearningModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + typename SparseMdpExplorationModelChecker::StateType SparseMdpExplorationModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { std::vector> const& row = explorationInformation.getRowOfMatrix(chosenAction); // if (row.size() == 1) { // return row.front().getColumn(); @@ -364,7 +359,7 @@ namespace storm { } template - bool SparseMdpLearningModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { + bool SparseMdpExplorationModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { ++stats.numberOfPrecomputations; // Outline: @@ -498,7 +493,7 @@ namespace storm { } template - void SparseMdpLearningModelChecker::collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { + void SparseMdpExplorationModelChecker::collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { bool containsTargetState = false; // Now we record all actions leaving the EC. @@ -567,7 +562,7 @@ namespace storm { } template - ValueType SparseMdpLearningModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType SparseMdpExplorationModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { ValueType result = storm::utility::zero(); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { result += element.getValue() * bounds.getLowerBoundForState(element.getColumn(), explorationInformation); @@ -576,7 +571,7 @@ namespace storm { } template - ValueType SparseMdpLearningModelChecker::computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType SparseMdpExplorationModelChecker::computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { ValueType result = storm::utility::zero(); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { result += element.getValue() * bounds.getUpperBoundForState(element.getColumn(), explorationInformation); @@ -585,7 +580,7 @@ namespace storm { } template - std::pair SparseMdpLearningModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + std::pair SparseMdpExplorationModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { // TODO: take into account self-loops? std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { @@ -596,7 +591,7 @@ namespace storm { } template - std::pair SparseMdpLearningModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + std::pair SparseMdpExplorationModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { StateType group = explorationInformation.getRowGroup(currentStateId); std::pair result = getLowestBounds(explorationInformation.optimizationDirection); for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { @@ -607,7 +602,7 @@ namespace storm { } template - void SparseMdpLearningModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + void SparseMdpExplorationModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { stack.pop_back(); while (!stack.empty()) { updateProbabilityOfAction(stack.back().first, stack.back().second, explorationInformation, bounds); @@ -616,7 +611,7 @@ namespace storm { } template - void SparseMdpLearningModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + void SparseMdpExplorationModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { // Compute the new lower/upper values of the action. std::pair newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds); @@ -651,7 +646,7 @@ namespace storm { } template - ValueType SparseMdpLearningModelChecker::computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType SparseMdpExplorationModelChecker::computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { ValueType bound = getLowestBound(explorationInformation.optimizationDirection); ActionType group = explorationInformation.getRowGroup(state); @@ -670,13 +665,13 @@ namespace storm { } template - std::pair SparseMdpLearningModelChecker::getLowestBounds(storm::OptimizationDirection const& direction) const { + std::pair SparseMdpExplorationModelChecker::getLowestBounds(storm::OptimizationDirection const& direction) const { ValueType val = getLowestBound(direction); return std::make_pair(val, val); } template - ValueType SparseMdpLearningModelChecker::getLowestBound(storm::OptimizationDirection const& direction) const { + ValueType SparseMdpExplorationModelChecker::getLowestBound(storm::OptimizationDirection const& direction) const { if (direction == storm::OptimizationDirection::Maximize) { return storm::utility::zero(); } else { @@ -685,7 +680,7 @@ namespace storm { } template - std::pair SparseMdpLearningModelChecker::combineBounds(storm::OptimizationDirection const& direction, std::pair const& bounds1, std::pair const& bounds2) const { + std::pair SparseMdpExplorationModelChecker::combineBounds(storm::OptimizationDirection const& direction, std::pair const& bounds1, std::pair const& bounds2) const { if (direction == storm::OptimizationDirection::Maximize) { return std::make_pair(std::max(bounds1.first, bounds2.first), std::max(bounds1.second, bounds2.second)); } else { @@ -693,6 +688,6 @@ namespace storm { } } - template class SparseMdpLearningModelChecker; + template class SparseMdpExplorationModelChecker; } } \ No newline at end of file diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h similarity index 64% rename from src/modelchecker/reachability/SparseMdpLearningModelChecker.h rename to src/modelchecker/exploration/SparseMdpExplorationModelChecker.h index 2e43ad582..f73f7b39f 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h @@ -1,5 +1,5 @@ -#ifndef STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ -#define STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ +#ifndef STORM_MODELCHECKER_EXPLORATION_SPARSEMDPEXPLORATIONMODELCHECKER_H_ +#define STORM_MODELCHECKER_EXPLORATION_SPARSEMDPEXPLORATIONMODELCHECKER_H_ #include @@ -13,7 +13,7 @@ #include "src/generator/VariableInformation.h" #include "src/settings/SettingsManager.h" -#include "src/settings/modules/LearningSettings.h" +#include "src/settings/modules/ExplorationSettings.h" #include "src/utility/macros.h" #include "src/utility/ConstantsComparator.h" @@ -37,7 +37,7 @@ namespace storm { namespace modelchecker { template - class SparseMdpLearningModelChecker : public AbstractModelChecker { + class SparseMdpExplorationModelChecker : public AbstractModelChecker { public: typedef uint32_t StateType; typedef uint32_t ActionType; @@ -46,18 +46,18 @@ namespace storm { typedef std::shared_ptr ActionSetPointer; typedef std::vector> StateActionStack; - SparseMdpLearningModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions); + SparseMdpExplorationModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions); virtual bool canHandle(CheckTask const& checkTask) const override; - virtual std::unique_ptr computeReachabilityProbabilities(CheckTask const& checkTask) override; + virtual std::unique_ptr computeUntilProbabilities(CheckTask const& checkTask) override; private: // A structure containing the data assembled during exploration. struct ExplorationInformation { - ExplorationInformation(uint_fast64_t bitsPerBucket, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localPrecomputation(false), numberOfExplorationStepsUntilPrecomputation(100000), numberOfSampledPathsUntilPrecomputation(), nextStateHeuristic(storm::settings::modules::LearningSettings::NextStateHeuristic::DifferenceWeightedProbability) { + ExplorationInformation(uint_fast64_t bitsPerBucket, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localPrecomputation(false), numberOfExplorationStepsUntilPrecomputation(100000), numberOfSampledPathsUntilPrecomputation(), nextStateHeuristic(storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability) { - storm::settings::modules::LearningSettings const& settings = storm::settings::learningSettings(); + storm::settings::modules::ExplorationSettings const& settings = storm::settings::explorationSettings(); localPrecomputation = settings.isLocalPrecomputationSet(); if (settings.isNumberOfSampledPathsUntilPrecomputationSet()) { numberOfSampledPathsUntilPrecomputation = settings.getNumberOfSampledPathsUntilPrecomputation(); @@ -83,7 +83,7 @@ namespace storm { uint_fast64_t numberOfExplorationStepsUntilPrecomputation; boost::optional numberOfSampledPathsUntilPrecomputation; - storm::settings::modules::LearningSettings::NextStateHeuristic nextStateHeuristic; + storm::settings::modules::ExplorationSettings::NextStateHeuristic nextStateHeuristic; void setInitialStates(std::vector const& initialStates) { stateStorage.initialStateIndices = initialStates; @@ -212,16 +212,16 @@ namespace storm { return !useLocalPrecomputation(); } - storm::settings::modules::LearningSettings::NextStateHeuristic const& getNextStateHeuristic() const { + storm::settings::modules::ExplorationSettings::NextStateHeuristic const& getNextStateHeuristic() const { return nextStateHeuristic; } bool useDifferenceWeightedProbabilityHeuristic() const { - return nextStateHeuristic == storm::settings::modules::LearningSettings::NextStateHeuristic::DifferenceWeightedProbability; + return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability; } bool useProbabilityHeuristic() const { - return nextStateHeuristic == storm::settings::modules::LearningSettings::NextStateHeuristic::Probability; + return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::Probability; } }; @@ -258,7 +258,7 @@ namespace storm { std::size_t totalNumberOfEcDetected; void printToStream(std::ostream& out, ExplorationInformation const& explorationInformation) const { - out << std::endl << "Learning statistics:" << std::endl; + 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; @@ -270,10 +270,14 @@ namespace storm { // A struct containing the data required for state exploration. struct StateGeneration { - StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, storm::expressions::Expression const& targetStateExpression) : generator(program, variableInformation, false), targetStateExpression(targetStateExpression) { + 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. } + void load(storm::generator::CompressedState const& state) { + generator.load(state); + } + std::vector getInitialStates() { return generator.getInitialStates(stateToIdCallback); } @@ -282,147 +286,23 @@ namespace storm { return generator.expand(stateToIdCallback); } + bool isConditionState() const { + return generator.satisfies(conditionStateExpression); + } + bool isTargetState() const { return generator.satisfies(targetStateExpression); } storm::generator::PrismNextStateGenerator generator; std::function stateToIdCallback; + storm::expressions::Expression conditionStateExpression; storm::expressions::Expression targetStateExpression; }; - // A struct containg the lower and upper bounds per state and action. - struct BoundValues { - std::vector lowerBoundsPerState; - std::vector upperBoundsPerState; - std::vector lowerBoundsPerAction; - std::vector upperBoundsPerAction; - - std::pair getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { - ActionType index = explorationInformation.getRowGroup(state); - if (index == explorationInformation.getUnexploredMarker()) { - return std::make_pair(storm::utility::zero(), storm::utility::one()); - } 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(); - } 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(); - } else { - return getUpperBoundForRowGroup(index); - } - } - - ValueType const& getUpperBoundForRowGroup(StateType const& rowGroup) const { - return upperBoundsPerState[rowGroup]; - } - - std::pair 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 bounds = getBoundsForState(state, explorationInformation); - return bounds.second - bounds.first; - } - - void initializeBoundsForNextState(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { - lowerBoundsPerState.push_back(vals.first); - upperBoundsPerState.push_back(vals.second); - } - - void initializeBoundsForNextAction(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { - 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 const& values) { - lowerBoundsPerAction[action] = values.first; - upperBoundsPerAction[action] = values.second; - } - - void setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { - StateType const& rowGroup = explorationInformation.getRowGroup(state); - setBoundsForRowGroup(rowGroup, values); - } - - void setBoundsForRowGroup(StateType const& rowGroup, std::pair 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; - } - }; - - storm::expressions::Expression getTargetStateExpression(storm::logic::Formula const& subformula) const; - std::function createStateToIdCallback(ExplorationInformation& explorationInformation) const; - std::tuple performLearningProcedure(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const; + std::tuple performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const; bool samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const; @@ -465,4 +345,4 @@ namespace storm { } } -#endif /* STORM_MODELCHECKER_REACHABILITY_SPARSEMDPLEARNINGMODELCHECKER_H_ */ \ No newline at end of file +#endif /* STORM_MODELCHECKER_EXPLORATION_SPARSEMDPEXPLORATIONMODELCHECKER_H_ */ \ No newline at end of file diff --git a/src/modelchecker/exploration/StateGeneration.cpp b/src/modelchecker/exploration/StateGeneration.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/modelchecker/exploration/StateGeneration.h b/src/modelchecker/exploration/StateGeneration.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/settings/SettingsManager.cpp b/src/settings/SettingsManager.cpp index effeaa658..a8ce73080 100644 --- a/src/settings/SettingsManager.cpp +++ b/src/settings/SettingsManager.cpp @@ -26,7 +26,7 @@ #include "src/settings/modules/ParametricSettings.h" #include "src/settings/modules/SparseDtmcEliminationModelCheckerSettings.h" #include "src/settings/modules/TopologicalValueIterationEquationSolverSettings.h" -#include "src/settings/modules/LearningSettings.h" +#include "src/settings/modules/ExplorationSettings.h" #include "src/utility/macros.h" #include "src/settings/Option.h" @@ -49,7 +49,7 @@ namespace storm { this->addModule(std::unique_ptr(new modules::TopologicalValueIterationEquationSolverSettings(*this))); this->addModule(std::unique_ptr(new modules::ParametricSettings(*this))); this->addModule(std::unique_ptr(new modules::SparseDtmcEliminationModelCheckerSettings(*this))); - this->addModule(std::unique_ptr(new modules::LearningSettings(*this))); + this->addModule(std::unique_ptr(new modules::ExplorationSettings(*this))); } SettingsManager::~SettingsManager() { @@ -550,8 +550,8 @@ namespace storm { return dynamic_cast(manager().getModule(storm::settings::modules::SparseDtmcEliminationModelCheckerSettings::moduleName)); } - storm::settings::modules::LearningSettings const& learningSettings() { - return dynamic_cast(manager().getModule(storm::settings::modules::LearningSettings::moduleName)); + storm::settings::modules::ExplorationSettings const& explorationSettings() { + return dynamic_cast(manager().getModule(storm::settings::modules::ExplorationSettings::moduleName)); } } } \ No newline at end of file diff --git a/src/settings/SettingsManager.h b/src/settings/SettingsManager.h index 52bc470d3..f6a1779d1 100644 --- a/src/settings/SettingsManager.h +++ b/src/settings/SettingsManager.h @@ -25,7 +25,7 @@ namespace storm { class TopologicalValueIterationEquationSolverSettings; class ParametricSettings; class SparseDtmcEliminationModelCheckerSettings; - class LearningSettings; + class ExplorationSettings; class ModuleSettings; } class Option; @@ -332,11 +332,11 @@ namespace storm { */ storm::settings::modules::SparseDtmcEliminationModelCheckerSettings const& sparseDtmcEliminationModelCheckerSettings(); - /* Retrieves the settings of the learning engine. + /* Retrieves the settings of the exploration engine. * - * @return An object that allows accessing the settings of the learning engine. + * @return An object that allows accessing the settings of the exploration engine. */ - storm::settings::modules::LearningSettings const& learningSettings(); + storm::settings::modules::ExplorationSettings const& explorationSettings(); } // namespace settings } // namespace storm diff --git a/src/settings/modules/LearningSettings.cpp b/src/settings/modules/ExplorationSettings.cpp similarity index 72% rename from src/settings/modules/LearningSettings.cpp rename to src/settings/modules/ExplorationSettings.cpp index 0ce44a8e4..a890a1f9e 100644 --- a/src/settings/modules/LearningSettings.cpp +++ b/src/settings/modules/ExplorationSettings.cpp @@ -1,4 +1,4 @@ -#include "src/settings/modules/LearningSettings.h" +#include "src/settings/modules/ExplorationSettings.h" #include "src/settings/modules/GeneralSettings.h" #include "src/settings/Option.h" #include "src/settings/OptionBuilder.h" @@ -10,13 +10,13 @@ namespace storm { namespace settings { namespace modules { - const std::string LearningSettings::moduleName = "learning"; - const std::string LearningSettings::precomputationTypeOptionName = "precomp"; - const std::string LearningSettings::numberOfExplorationStepsUntilPrecomputationOptionName = "stepsprecomp"; - const std::string LearningSettings::numberOfSampledPathsUntilPrecomputationOptionName = "pathsprecomp"; - const std::string LearningSettings::nextStateHeuristicOptionName = "nextstate"; + const std::string ExplorationSettings::moduleName = "exploration"; + const std::string ExplorationSettings::precomputationTypeOptionName = "precomp"; + const std::string ExplorationSettings::numberOfExplorationStepsUntilPrecomputationOptionName = "stepsprecomp"; + const std::string ExplorationSettings::numberOfSampledPathsUntilPrecomputationOptionName = "pathsprecomp"; + const std::string ExplorationSettings::nextStateHeuristicOptionName = "nextstate"; - LearningSettings::LearningSettings(storm::settings::SettingsManager& settingsManager) : ModuleSettings(settingsManager, moduleName) { + ExplorationSettings::ExplorationSettings(storm::settings::SettingsManager& settingsManager) : ModuleSettings(settingsManager, moduleName) { std::vector types = { "local", "global" }; this->addOption(storm::settings::OptionBuilder(moduleName, precomputationTypeOptionName, true, "Sets the kind of precomputation used. Available are: { local, global }.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(types)).setDefaultValueString("global").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, numberOfExplorationStepsUntilPrecomputationOptionName, false, "Sets the number of exploration steps to perform until a precomputation is triggered.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of exploration steps to perform.").setDefaultValueUnsignedInteger(100000).build()).build()); @@ -26,58 +26,58 @@ namespace storm { this->addOption(storm::settings::OptionBuilder(moduleName, nextStateHeuristicOptionName, true, "Sets the next-state heuristic to use. Available are: { probdiff, prob } where 'prob' samples according to the probabilities in the system and 'probdiff' weights the probabilities with the differences between the current bounds.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the heuristic to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(nextStateHeuristics)).setDefaultValueString("probdiff").build()).build()); } - bool LearningSettings::isLocalPrecomputationSet() const { + bool ExplorationSettings::isLocalPrecomputationSet() const { if (this->getOption(precomputationTypeOptionName).getArgumentByName("name").getValueAsString() == "local") { return true; } return false; } - bool LearningSettings::isGlobalPrecomputationSet() const { + bool ExplorationSettings::isGlobalPrecomputationSet() const { if (this->getOption(precomputationTypeOptionName).getArgumentByName("name").getValueAsString() == "global") { return true; } return false; } - LearningSettings::PrecomputationType LearningSettings::getPrecomputationType() const { + ExplorationSettings::PrecomputationType ExplorationSettings::getPrecomputationType() const { std::string typeAsString = this->getOption(precomputationTypeOptionName).getArgumentByName("name").getValueAsString(); if (typeAsString == "local") { - return LearningSettings::PrecomputationType::Local; + return ExplorationSettings::PrecomputationType::Local; } else if (typeAsString == "global") { - return LearningSettings::PrecomputationType::Global; + return ExplorationSettings::PrecomputationType::Global; } STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown precomputation type '" << typeAsString << "'."); } - uint_fast64_t LearningSettings::getNumberOfExplorationStepsUntilPrecomputation() const { + uint_fast64_t ExplorationSettings::getNumberOfExplorationStepsUntilPrecomputation() const { return this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getArgumentByName("count").getValueAsUnsignedInteger(); } - bool LearningSettings::isNumberOfSampledPathsUntilPrecomputationSet() const { + bool ExplorationSettings::isNumberOfSampledPathsUntilPrecomputationSet() const { return this->getOption(numberOfSampledPathsUntilPrecomputationOptionName).getHasOptionBeenSet(); } - uint_fast64_t LearningSettings::getNumberOfSampledPathsUntilPrecomputation() const { + uint_fast64_t ExplorationSettings::getNumberOfSampledPathsUntilPrecomputation() const { return this->getOption(numberOfSampledPathsUntilPrecomputationOptionName).getArgumentByName("count").getValueAsUnsignedInteger(); } - LearningSettings::NextStateHeuristic LearningSettings::getNextStateHeuristic() const { + ExplorationSettings::NextStateHeuristic ExplorationSettings::getNextStateHeuristic() const { std::string nextStateHeuristicAsString = this->getOption(nextStateHeuristicOptionName).getArgumentByName("name").getValueAsString(); if (nextStateHeuristicAsString == "probdiff") { - return LearningSettings::NextStateHeuristic::DifferenceWeightedProbability; + return ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability; } else if (nextStateHeuristicAsString == "prob") { - return LearningSettings::NextStateHeuristic::Probability; + return ExplorationSettings::NextStateHeuristic::Probability; } STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown next-state heuristic '" << nextStateHeuristicAsString << "'."); } - bool LearningSettings::check() const { + bool ExplorationSettings::check() const { bool optionsSet = this->getOption(precomputationTypeOptionName).getHasOptionBeenSet() || this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getHasOptionBeenSet() || this->getOption(numberOfSampledPathsUntilPrecomputationOptionName).getHasOptionBeenSet() || this->getOption(nextStateHeuristicOptionName).getHasOptionBeenSet(); - STORM_LOG_WARN_COND(storm::settings::generalSettings().getEngine() == storm::settings::modules::GeneralSettings::Engine::Learning || !optionsSet, "Learning engine is not selected, so setting options for it has no effect."); + STORM_LOG_WARN_COND(storm::settings::generalSettings().getEngine() == storm::settings::modules::GeneralSettings::Engine::Exploration || !optionsSet, "Exploration engine is not selected, so setting options for it has no effect."); return true; } } // namespace modules diff --git a/src/settings/modules/LearningSettings.h b/src/settings/modules/ExplorationSettings.h similarity index 88% rename from src/settings/modules/LearningSettings.h rename to src/settings/modules/ExplorationSettings.h index 2aa0d61a7..10dcaf080 100644 --- a/src/settings/modules/LearningSettings.h +++ b/src/settings/modules/ExplorationSettings.h @@ -1,5 +1,5 @@ -#ifndef STORM_SETTINGS_MODULES_LEARNINGSETTINGS_H_ -#define STORM_SETTINGS_MODULES_LEARNINGSETTINGS_H_ +#ifndef STORM_SETTINGS_MODULES_EXPLORATIONSETTINGS_H_ +#define STORM_SETTINGS_MODULES_EXPLORATIONSETTINGS_H_ #include "src/settings/modules/ModuleSettings.h" @@ -8,9 +8,9 @@ namespace storm { namespace modules { /*! - * This class represents the learning settings. + * This class represents the exploration settings. */ - class LearningSettings : public ModuleSettings { + class ExplorationSettings : public ModuleSettings { public: // An enumeration of all available precomputation types. enum class PrecomputationType { Local, Global }; @@ -19,11 +19,11 @@ namespace storm { enum class NextStateHeuristic { DifferenceWeightedProbability, Probability }; /*! - * Creates a new set of learning settings that is managed by the given manager. + * Creates a new set of exploration settings that is managed by the given manager. * * @param settingsManager The responsible manager. */ - LearningSettings(storm::settings::SettingsManager& settingsManager); + ExplorationSettings(storm::settings::SettingsManager& settingsManager); /*! * Retrieves whether local precomputation is to be used. @@ -90,4 +90,4 @@ namespace storm { } // namespace settings } // namespace storm -#endif /* STORM_SETTINGS_MODULES_LEARNINGSETTINGS_H_ */ +#endif /* STORM_SETTINGS_MODULES_EXPLORATIONSETTINGS_H_ */ diff --git a/src/settings/modules/GeneralSettings.cpp b/src/settings/modules/GeneralSettings.cpp index 85dc49c2d..a5e878f0b 100644 --- a/src/settings/modules/GeneralSettings.cpp +++ b/src/settings/modules/GeneralSettings.cpp @@ -103,9 +103,9 @@ namespace storm { .addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "The file from which to read the choice labels.").addValidationFunctionString(storm::settings::ArgumentValidators::existingReadableFileValidator()).build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, dontFixDeadlockOptionName, false, "If the model contains deadlock states, they need to be fixed by setting this option.").setShortName(dontFixDeadlockOptionShortName).build()); - std::vector engines = {"sparse", "hybrid", "dd", "learn", "abs"}; + std::vector engines = {"sparse", "hybrid", "dd", "expl", "abs"}; this->addOption(storm::settings::OptionBuilder(moduleName, engineOptionName, false, "Sets which engine is used for model building and model checking.").setShortName(engineOptionShortName) - .addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the engine to use. Available are {sparse, hybrid, dd, learn, abs}.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(engines)).setDefaultValueString("sparse").build()).build()); + .addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the engine to use. Available are {sparse, hybrid, dd, expl, abs}.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(engines)).setDefaultValueString("sparse").build()).build()); std::vector linearEquationSolver = {"gmm++", "native"}; this->addOption(storm::settings::OptionBuilder(moduleName, eqSolverOptionName, false, "Sets which solver is preferred for solving systems of linear equations.") @@ -367,8 +367,8 @@ namespace storm { engine = GeneralSettings::Engine::Hybrid; } else if (engineStr == "dd") { engine = GeneralSettings::Engine::Dd; - } else if (engineStr == "learn") { - engine = GeneralSettings::Engine::Learning; + } else if (engineStr == "expl") { + engine = GeneralSettings::Engine::Exploration; } else if (engineStr == "abs") { engine = GeneralSettings::Engine::AbstractionRefinement; } else { diff --git a/src/settings/modules/GeneralSettings.h b/src/settings/modules/GeneralSettings.h index 4eb493e4d..522d169af 100644 --- a/src/settings/modules/GeneralSettings.h +++ b/src/settings/modules/GeneralSettings.h @@ -28,7 +28,7 @@ namespace storm { public: // An enumeration of all engines. enum class Engine { - Sparse, Hybrid, Dd, Learning, AbstractionRefinement + Sparse, Hybrid, Dd, Exploration, AbstractionRefinement }; /*! diff --git a/src/utility/storm.h b/src/utility/storm.h index aca1a1cac..d5713a142 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -57,7 +57,7 @@ #include "src/modelchecker/prctl/SymbolicDtmcPrctlModelChecker.h" #include "src/modelchecker/prctl/SymbolicMdpPrctlModelChecker.h" #include "src/modelchecker/reachability/SparseDtmcEliminationModelChecker.h" -#include "src/modelchecker/reachability/SparseMdpLearningModelChecker.h" +#include "src/modelchecker/exploration/SparseMdpExplorationModelChecker.h" #include "src/modelchecker/csl/SparseCtmcCslModelChecker.h" #include "src/modelchecker/csl/HybridCtmcCslModelChecker.h" #include "src/modelchecker/csl/SparseMarkovAutomatonCslModelChecker.h" diff --git a/src/utility/vector.h b/src/utility/vector.h index 31b1a5493..731cc4a0d 100644 --- a/src/utility/vector.h +++ b/src/utility/vector.h @@ -22,8 +22,6 @@ namespace storm { namespace utility { namespace vector { - - /*! * Sets the provided values at the provided positions in the given vector. * From d2d1ebdb1ab96d4d9624bdc90d24a03bc779cf33 Mon Sep 17 00:00:00 2001 From: TimQu Date: Wed, 13 Apr 2016 15:59:25 +0200 Subject: [PATCH 26/31] test didn't compile due to recent changes in carl::rationalize Former-commit-id: 81af3a0f52924b36ccfacd7f286ef7d8f29d1130 --- test/functional/utility/ModelInstantiatorTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/utility/ModelInstantiatorTest.cpp b/test/functional/utility/ModelInstantiatorTest.cpp index fe2ee4a7c..24512d886 100644 --- a/test/functional/utility/ModelInstantiatorTest.cpp +++ b/test/functional/utility/ModelInstantiatorTest.cpp @@ -77,8 +77,8 @@ TEST(ModelInstantiatorTest, BrpProb) { ASSERT_NE(pL, carl::Variable::NO_VARIABLE); storm::Variable const& pK = carl::VariablePool::getInstance().findVariableWithName("pK"); ASSERT_NE(pK, carl::Variable::NO_VARIABLE); - valuation.insert(std::make_pair(pL,carl::rationalize(1))); - valuation.insert(std::make_pair(pK,carl::rationalize(1))); + valuation.insert(std::make_pair(pL,carl::rationalize(1.0))); + valuation.insert(std::make_pair(pK,carl::rationalize(1.0))); storm::models::sparse::Dtmc const& instantiated(modelInstantiator.instantiate(valuation)); @@ -110,7 +110,7 @@ TEST(ModelInstantiatorTest, BrpProb) { ASSERT_NE(pL, carl::Variable::NO_VARIABLE); storm::Variable const& pK = carl::VariablePool::getInstance().findVariableWithName("pK"); ASSERT_NE(pK, carl::Variable::NO_VARIABLE); - valuation.insert(std::make_pair(pL,carl::rationalize(1))); + valuation.insert(std::make_pair(pL,carl::rationalize(1.0))); valuation.insert(std::make_pair(pK,carl::rationalize(0.9))); storm::models::sparse::Dtmc const& instantiated(modelInstantiator.instantiate(valuation)); From f9193325d4a6f370c165c8eed59d557f3e814a88 Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 13 Apr 2016 18:12:52 +0200 Subject: [PATCH 27/31] further refactoring of exploration (better name than 'learning') based engine Former-commit-id: 72622d821dda06dfeace73477917fd1da5e9a05c --- src/modelchecker/exploration/BoundValues.cpp | 0 src/modelchecker/exploration/BoundValues.h | 137 -------- src/modelchecker/exploration/Bounds.cpp | 155 +++++++++ src/modelchecker/exploration/Bounds.h | 76 +++++ .../exploration/ExplorationInformation.cpp | 209 ++++++++++++ .../exploration/ExplorationInformation.h | 120 +++++++ .../SparseMdpExplorationModelChecker.cpp | 81 +++-- .../SparseMdpExplorationModelChecker.h | 308 ++---------------- .../exploration/StateGeneration.cpp | 45 +++ .../exploration/StateGeneration.h | 44 +++ src/modelchecker/exploration/Statistics.cpp | 46 +++ src/modelchecker/exploration/Statistics.h | 44 +++ 12 files changed, 810 insertions(+), 455 deletions(-) delete mode 100644 src/modelchecker/exploration/BoundValues.cpp delete mode 100644 src/modelchecker/exploration/BoundValues.h create mode 100644 src/modelchecker/exploration/Bounds.cpp create mode 100644 src/modelchecker/exploration/Bounds.h create mode 100644 src/modelchecker/exploration/Statistics.cpp create mode 100644 src/modelchecker/exploration/Statistics.h diff --git a/src/modelchecker/exploration/BoundValues.cpp b/src/modelchecker/exploration/BoundValues.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/modelchecker/exploration/BoundValues.h b/src/modelchecker/exploration/BoundValues.h deleted file mode 100644 index 3640fa9c6..000000000 --- a/src/modelchecker/exploration/BoundValues.h +++ /dev/null @@ -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 lowerBoundsPerState; - std::vector upperBoundsPerState; - std::vector lowerBoundsPerAction; - std::vector upperBoundsPerAction; - - std::pair getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { - ActionType index = explorationInformation.getRowGroup(state); - if (index == explorationInformation.getUnexploredMarker()) { - return std::make_pair(storm::utility::zero(), storm::utility::one()); - } 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(); - } 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(); - } else { - return getUpperBoundForRowGroup(index); - } - } - - ValueType const& getUpperBoundForRowGroup(StateType const& rowGroup) const { - return upperBoundsPerState[rowGroup]; - } - - std::pair 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 bounds = getBoundsForState(state, explorationInformation); - return bounds.second - bounds.first; - } - - void initializeBoundsForNextState(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { - lowerBoundsPerState.push_back(vals.first); - upperBoundsPerState.push_back(vals.second); - } - - void initializeBoundsForNextAction(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())) { - 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 const& values) { - lowerBoundsPerAction[action] = values.first; - upperBoundsPerAction[action] = values.second; - } - - void setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { - StateType const& rowGroup = explorationInformation.getRowGroup(state); - setBoundsForRowGroup(rowGroup, values); - } - - void setBoundsForRowGroup(StateType const& rowGroup, std::pair 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; - } - }; - - } - } -} \ No newline at end of file diff --git a/src/modelchecker/exploration/Bounds.cpp b/src/modelchecker/exploration/Bounds.cpp new file mode 100644 index 000000000..32ad0cb92 --- /dev/null +++ b/src/modelchecker/exploration/Bounds.cpp @@ -0,0 +1,155 @@ +#include "src/modelchecker/exploration/Bounds.h" + +#include "src/modelchecker/exploration/ExplorationInformation.h" + +namespace storm { + namespace modelchecker { + namespace exploration_detail { + + template + std::pair Bounds::getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { + ActionType index = explorationInformation.getRowGroup(state); + if (index == explorationInformation.getUnexploredMarker()) { + return std::make_pair(storm::utility::zero(), storm::utility::one()); + } else { + return boundsPerState[index]; + } + } + + template + std::pair const& Bounds::getBoundsForExploredState(StateType const& state, ExplorationInformation const& explorationInformation) const { + ActionType index = explorationInformation.getRowGroup(state); + return boundsPerState[index]; + } + + template + ValueType Bounds::getLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const { + ActionType index = explorationInformation.getRowGroup(state); + if (index == explorationInformation.getUnexploredMarker()) { + return storm::utility::zero(); + } else { + return getLowerBoundForRowGroup(index); + } + } + + template + ValueType const& Bounds::getLowerBoundForRowGroup(StateType const& rowGroup) const { + return boundsPerState[rowGroup].first; + } + + template + ValueType Bounds::getUpperBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const { + ActionType index = explorationInformation.getRowGroup(state); + if (index == explorationInformation.getUnexploredMarker()) { + return storm::utility::one(); + } else { + return getUpperBoundForRowGroup(index); + } + } + + template + ValueType const& Bounds::getUpperBoundForRowGroup(StateType const& rowGroup) const { + return boundsPerState[rowGroup].second; + } + + template + std::pair const& Bounds::getBoundsForAction(ActionType const& action) const { + return boundsPerAction[action]; + } + + template + ValueType const& Bounds::getLowerBoundForAction(ActionType const& action) const { + return boundsPerAction[action].first; + } + + template + ValueType const& Bounds::getUpperBoundForAction(ActionType const& action) const { + return boundsPerAction[action].second; + } + + template + ValueType const& Bounds::getBoundForAction(storm::OptimizationDirection const& direction, ActionType const& action) const { + if (direction == storm::OptimizationDirection::Maximize) { + return getUpperBoundForAction(action); + } else { + return getLowerBoundForAction(action); + } + } + + template + ValueType Bounds::getDifferenceOfStateBounds(StateType const& state, ExplorationInformation const& explorationInformation) const { + std::pair const& bounds = getBoundsForExploredState(state, explorationInformation); + return bounds.second - bounds.first; + } + + template + void Bounds::initializeBoundsForNextState(std::pair const& vals) { + boundsPerState.push_back(vals); + } + + template + void Bounds::initializeBoundsForNextAction(std::pair const& vals) { + boundsPerAction.push_back(vals); + } + + template + void Bounds::setLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value) { + setLowerBoundForRowGroup(explorationInformation.getRowGroup(state), value); + } + + template + void Bounds::setLowerBoundForRowGroup(StateType const& group, ValueType const& value) { + boundsPerState[group].first = value; + } + + template + void Bounds::setUpperBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value) { + setUpperBoundForRowGroup(explorationInformation.getRowGroup(state), value); + } + + template + void Bounds::setUpperBoundForRowGroup(StateType const& group, ValueType const& value) { + boundsPerState[group].second = value; + } + + template + void Bounds::setBoundsForAction(ActionType const& action, std::pair const& values) { + boundsPerAction[action] = values; + } + + template + void Bounds::setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { + StateType const& rowGroup = explorationInformation.getRowGroup(state); + setBoundsForRowGroup(rowGroup, values); + } + + template + void Bounds::setBoundsForRowGroup(StateType const& rowGroup, std::pair const& values) { + boundsPerState[rowGroup] = values; + } + + template + bool Bounds::setLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation 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 + bool Bounds::setUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation 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; + + } + } +} diff --git a/src/modelchecker/exploration/Bounds.h b/src/modelchecker/exploration/Bounds.h new file mode 100644 index 000000000..27e0979a5 --- /dev/null +++ b/src/modelchecker/exploration/Bounds.h @@ -0,0 +1,76 @@ +#ifndef STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_BOUNDS_H_ +#define STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_BOUNDS_H_ + +#include +#include + +#include "src/solver/OptimizationDirection.h" + +#include "src/utility/constants.h" + +namespace storm { + namespace modelchecker { + namespace exploration_detail { + + template + class ExplorationInformation; + + template + class Bounds { + public: + typedef StateType ActionType; + + std::pair getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const; + + std::pair const& getBoundsForExploredState(StateType const& state, ExplorationInformation const& explorationInformation) const; + + ValueType getLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const; + + ValueType const& getLowerBoundForRowGroup(StateType const& rowGroup) const; + + ValueType getUpperBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const; + + ValueType const& getUpperBoundForRowGroup(StateType const& rowGroup) const; + + std::pair 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 const& explorationInformation) const; + + void initializeBoundsForNextState(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())); + + void initializeBoundsForNextAction(std::pair const& vals = std::pair(storm::utility::zero(), storm::utility::one())); + + void setLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value); + + void setLowerBoundForRowGroup(StateType const& group, ValueType const& value); + + void setUpperBoundForState(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& value); + + void setUpperBoundForRowGroup(StateType const& group, ValueType const& value); + + void setBoundsForAction(ActionType const& action, std::pair const& values); + + void setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values); + + void setBoundsForRowGroup(StateType const& rowGroup, std::pair const& values); + + bool setLowerBoundOfStateIfGreaterThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newLowerValue); + + bool setUpperBoundOfStateIfLessThanOld(StateType const& state, ExplorationInformation const& explorationInformation, ValueType const& newUpperValue); + + private: + std::vector> boundsPerState; + std::vector> boundsPerAction; + }; + + } + } +} + +#endif /* STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_BOUNDS_H_ */ \ No newline at end of file diff --git a/src/modelchecker/exploration/ExplorationInformation.cpp b/src/modelchecker/exploration/ExplorationInformation.cpp index e69de29bb..12715f307 100644 --- a/src/modelchecker/exploration/ExplorationInformation.cpp +++ b/src/modelchecker/exploration/ExplorationInformation.cpp @@ -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 + ExplorationInformation::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 + void ExplorationInformation::setInitialStates(std::vector const& initialStates) { + stateStorage.initialStateIndices = initialStates; + } + + template + StateType ExplorationInformation::getFirstInitialState() const { + return stateStorage.initialStateIndices.front(); + } + + template + std::size_t ExplorationInformation::getNumberOfInitialStates() const { + return stateStorage.initialStateIndices.size(); + } + + template + void ExplorationInformation::addUnexploredState(storm::generator::CompressedState const& compressedState) { + stateToRowGroupMapping.push_back(unexploredMarker); + unexploredStates[stateStorage.numberOfStates] = compressedState; + ++stateStorage.numberOfStates; + } + + template + void ExplorationInformation::assignStateToRowGroup(StateType const& state, ActionType const& rowGroup) { + stateToRowGroupMapping[state] = rowGroup; + } + + template + StateType ExplorationInformation::assignStateToNextRowGroup(StateType const& state) { + stateToRowGroupMapping[state] = rowGroupIndices.size() - 1; + return stateToRowGroupMapping[state]; + } + + template + StateType ExplorationInformation::getNextRowGroup() const { + return rowGroupIndices.size() - 1; + } + + template + void ExplorationInformation::newRowGroup(ActionType const& action) { + rowGroupIndices.push_back(action); + } + + template + void ExplorationInformation::newRowGroup() { + newRowGroup(matrix.size()); + } + + template + std::size_t ExplorationInformation::getNumberOfUnexploredStates() const { + return unexploredStates.size(); + } + + template + std::size_t ExplorationInformation::getNumberOfDiscoveredStates() const { + return stateStorage.numberOfStates; + } + + template + StateType const& ExplorationInformation::getRowGroup(StateType const& state) const { + return stateToRowGroupMapping[state]; + } + + template + StateType const& ExplorationInformation::getUnexploredMarker() const { + return unexploredMarker; + } + + template + bool ExplorationInformation::isUnexplored(StateType const& state) const { + return stateToRowGroupMapping[state] == unexploredMarker; + } + + template + bool ExplorationInformation::isTerminal(StateType const& state) const { + return terminalStates.find(state) != terminalStates.end(); + } + + template + typename ExplorationInformation::ActionType const& ExplorationInformation::getStartRowOfGroup(StateType const& group) const { + return rowGroupIndices[group]; + } + + template + std::size_t ExplorationInformation::getRowGroupSize(StateType const& group) const { + return rowGroupIndices[group + 1] - rowGroupIndices[group]; + } + + template + bool ExplorationInformation::onlyOneActionAvailable(StateType const& group) const { + return getRowGroupSize(group) == 1; + } + + template + void ExplorationInformation::addTerminalState(StateType const& state) { + terminalStates.insert(state); + } + + template + std::vector>& ExplorationInformation::getRowOfMatrix(ActionType const& row) { + return matrix[row]; + } + + template + std::vector> const& ExplorationInformation::getRowOfMatrix(ActionType const& row) const { + return matrix[row]; + } + + template + void ExplorationInformation::addRowsToMatrix(std::size_t const& count) { + matrix.resize(matrix.size() + count); + } + + template + bool ExplorationInformation::maximize() const { + return optimizationDirection == storm::OptimizationDirection::Maximize; + } + + template + bool ExplorationInformation::minimize() const { + return !maximize(); + } + + template + bool ExplorationInformation::performPrecomputationExcessiveExplorationSteps(std::size_t& numberExplorationStepsSinceLastPrecomputation) const { + bool result = numberExplorationStepsSinceLastPrecomputation > numberOfExplorationStepsUntilPrecomputation; + if (result) { + numberExplorationStepsSinceLastPrecomputation = 0; + } + return result; + } + + template + bool ExplorationInformation::performPrecomputationExcessiveSampledPaths(std::size_t& numberOfSampledPathsSinceLastPrecomputation) const { + if (!numberOfSampledPathsUntilPrecomputation) { + return false; + } else { + bool result = numberOfSampledPathsSinceLastPrecomputation > numberOfSampledPathsUntilPrecomputation.get(); + if (result) { + numberOfSampledPathsSinceLastPrecomputation = 0; + } + return result; + } + } + + template + bool ExplorationInformation::useLocalPrecomputation() const { + return localPrecomputation; + } + + template + bool ExplorationInformation::useGlobalPrecomputation() const { + return !useLocalPrecomputation(); + } + + template + storm::settings::modules::ExplorationSettings::NextStateHeuristic const& ExplorationInformation::getNextStateHeuristic() const { + return nextStateHeuristic; + } + + template + bool ExplorationInformation::useDifferenceWeightedProbabilityHeuristic() const { + return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability; + } + + template + bool ExplorationInformation::useProbabilityHeuristic() const { + return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::Probability; + } + + template + storm::OptimizationDirection const& ExplorationInformation::getOptimizationDirection() const { + return optimizationDirection; + } + + template + void ExplorationInformation::setOptimizationDirection(storm::OptimizationDirection const& direction) { + optimizationDirection = direction; + } + + template class ExplorationInformation; + } + } +} \ No newline at end of file diff --git a/src/modelchecker/exploration/ExplorationInformation.h b/src/modelchecker/exploration/ExplorationInformation.h index e69de29bb..b073352cd 100644 --- a/src/modelchecker/exploration/ExplorationInformation.h +++ b/src/modelchecker/exploration/ExplorationInformation.h @@ -0,0 +1,120 @@ +#ifndef STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_EXPLORATIONINFORMATION_H_ +#define STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_EXPLORATIONINFORMATION_H_ + +#include +#include +#include + +#include +#include + +#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 + class ExplorationInformation { + public: + typedef StateType ActionType; + typedef boost::container::flat_set StateSet; + + ExplorationInformation(uint_fast64_t bitsPerBucket, storm::OptimizationDirection const& direction, ActionType const& unexploredMarker = std::numeric_limits::max()); + + void setInitialStates(std::vector 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>& getRowOfMatrix(ActionType const& row); + + std::vector> 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 stateStorage; + + std::vector>> matrix; + std::vector rowGroupIndices; + + std::vector stateToRowGroupMapping; + StateType unexploredMarker; + std::unordered_map unexploredStates; + + storm::OptimizationDirection optimizationDirection; + StateSet terminalStates; + + bool localPrecomputation; + std::size_t numberOfExplorationStepsUntilPrecomputation; + boost::optional numberOfSampledPathsUntilPrecomputation; + + storm::settings::modules::ExplorationSettings::NextStateHeuristic nextStateHeuristic; + }; + } + } +} + +#endif /* STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_EXPLORATIONINFORMATION_H_ */ \ No newline at end of file diff --git a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp index 037d97800..4ad5b5613 100644 --- a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp +++ b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp @@ -1,20 +1,30 @@ #include "src/modelchecker/exploration/SparseMdpExplorationModelChecker.h" +#include "src/modelchecker/exploration/ExplorationInformation.h" +#include "src/modelchecker/exploration/StateGeneration.h" +#include "src/modelchecker/exploration/Bounds.h" +#include "src/modelchecker/exploration/Statistics.h" + +#include "src/generator/CompressedState.h" + #include "src/storage/SparseMatrix.h" #include "src/storage/MaximalEndComponentDecomposition.h" -#include "src/storage/expressions/SimpleValuation.h" -#include "src/logic/FragmentSpecification.h" +#include "src/storage/prism/Program.h" -#include "src/utility/prism.h" +#include "src/logic/FragmentSpecification.h" #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "src/models/sparse/StandardRewardModel.h" +#include "src/settings/SettingsManager.h" #include "src/settings/modules/GeneralSettings.h" +#include "src/utility/macros.h" +#include "src/utility/constants.h" #include "src/utility/graph.h" +#include "src/utility/prism.h" #include "src/exceptions/InvalidOperationException.h" #include "src/exceptions/InvalidPropertyException.h" @@ -43,16 +53,15 @@ namespace storm { STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "For nondeterministic systems, an optimization direction (min/max) must be given in the property."); std::map labelToExpressionMapping = program.getLabelToExpressionMapping(); - StateGeneration stateGeneration(program, variableInformation, conditionFormula.toExpression(program.getManager(), labelToExpressionMapping), targetFormula.toExpression(program.getManager(), labelToExpressionMapping)); + StateGeneration stateGeneration(program, variableInformation, conditionFormula.toExpression(program.getManager(), labelToExpressionMapping), targetFormula.toExpression(program.getManager(), labelToExpressionMapping)); - ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), storm::settings::explorationSettings().getNumberOfExplorationStepsUntilPrecomputation()); - explorationInformation.optimizationDirection = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; + ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize); // The first row group starts at action 0. explorationInformation.newRowGroup(0); // Create a callback for the next-state generator to enable it to request the index of states. - stateGeneration.stateToIdCallback = createStateToIdCallback(explorationInformation); + stateGeneration.setStateToIdCallback(createStateToIdCallback(explorationInformation)); // Compute and return result. std::tuple boundsForInitialState = performExploration(stateGeneration, explorationInformation); @@ -60,9 +69,9 @@ namespace storm { } template - std::function::StateType (storm::generator::CompressedState const&)> SparseMdpExplorationModelChecker::createStateToIdCallback(ExplorationInformation& explorationInformation) const { + std::function::StateType (storm::generator::CompressedState const&)> SparseMdpExplorationModelChecker::createStateToIdCallback(ExplorationInformation& explorationInformation) const { return [&explorationInformation,this] (storm::generator::CompressedState const& state) -> StateType { - StateType newIndex = explorationInformation.stateStorage.numberOfStates; + StateType newIndex = explorationInformation.getNumberOfDiscoveredStates(); // Check, if the state was already registered. std::pair actualIndexBucketPair = explorationInformation.stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); @@ -76,7 +85,7 @@ namespace storm { } template - std::tuple::StateType, ValueType, ValueType> SparseMdpExplorationModelChecker::performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { + std::tuple::StateType, ValueType, ValueType> SparseMdpExplorationModelChecker::performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { // Generate the initial state so we know where to start the simulation. explorationInformation.setInitialStates(stateGeneration.getInitialStates()); @@ -84,13 +93,13 @@ namespace storm { StateType initialStateIndex = explorationInformation.getFirstInitialState(); // Create a structure that holds the bounds for the states and actions. - BoundValues bounds; + Bounds bounds; // Create a stack that is used to track the path we sampled. StateActionStack stack; // Now perform the actual sampling. - Statistics stats; + Statistics stats; bool convergenceCriterionMet = false; while (!convergenceCriterionMet) { bool result = samplePathFromInitialState(stateGeneration, explorationInformation, stack, bounds, stats); @@ -130,7 +139,7 @@ namespace storm { } template - bool SparseMdpExplorationModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const { + bool SparseMdpExplorationModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, Bounds& bounds, Statistics& stats) const { // Start the search from the initial state. stack.push_back(std::make_pair(explorationInformation.getFirstInitialState(), 0)); @@ -192,7 +201,7 @@ namespace storm { } template - bool SparseMdpExplorationModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { + bool SparseMdpExplorationModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const { bool isTerminalState = false; bool isTargetState = false; @@ -242,7 +251,7 @@ namespace storm { ActionType currentAction = 0; // Retrieve the lowest state bounds (wrt. to the current optimization direction). - std::pair stateBounds = getLowestBounds(explorationInformation.optimizationDirection); + std::pair stateBounds = getLowestBounds(explorationInformation.getOptimizationDirection()); for (auto const& choice : behavior) { for (auto const& entry : choice) { @@ -251,7 +260,7 @@ namespace storm { std::pair actionBounds = computeBoundsOfAction(startRow + currentAction, explorationInformation, bounds); bounds.initializeBoundsForNextAction(actionBounds); - stateBounds = combineBounds(explorationInformation.optimizationDirection, stateBounds, actionBounds); + stateBounds = combineBounds(explorationInformation.getOptimizationDirection(), stateBounds, actionBounds); STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << bounds.getLowerBoundForAction(startRow + currentAction) << " and " << bounds.getUpperBoundForAction(startRow + currentAction) << "."); @@ -293,7 +302,7 @@ namespace storm { } template - typename SparseMdpExplorationModelChecker::ActionType SparseMdpExplorationModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + typename SparseMdpExplorationModelChecker::ActionType SparseMdpExplorationModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds& bounds) const { // Determine the values of all available actions. std::vector> actionValues; StateType rowGroup = explorationInformation.getRowGroup(currentStateId); @@ -307,7 +316,7 @@ namespace storm { STORM_LOG_TRACE("Sampling from actions leaving the state."); for (uint32_t row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) { - actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.optimizationDirection, row))); + actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.getOptimizationDirection(), row))); } STORM_LOG_ASSERT(!actionValues.empty(), "Values for actions must not be empty."); @@ -331,7 +340,7 @@ namespace storm { } template - typename SparseMdpExplorationModelChecker::StateType SparseMdpExplorationModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + typename SparseMdpExplorationModelChecker::StateType SparseMdpExplorationModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { std::vector> const& row = explorationInformation.getRowOfMatrix(chosenAction); // if (row.size() == 1) { // return row.front().getColumn(); @@ -359,7 +368,7 @@ namespace storm { } template - bool SparseMdpExplorationModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const { + bool SparseMdpExplorationModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const { ++stats.numberOfPrecomputations; // Outline: @@ -383,9 +392,9 @@ namespace storm { auto newEnd = std::unique(relevantStates.begin(), relevantStates.end()); relevantStates.resize(std::distance(relevantStates.begin(), newEnd)); } else { - for (StateType state = 0; state < explorationInformation.stateStorage.numberOfStates; ++state) { - // Add the state to the relevant states if it's unexplored. Additionally, if we are computing minimal - // probabilities, we only consider it relevant if it's not a target state. + for (StateType state = 0; state < explorationInformation.getNumberOfDiscoveredStates(); ++state) { + // Add the state to the relevant states if they are not unexplored. Additionally, if we are + // computing minimal probabilities, we only consider it relevant if it's not a target state. if (!explorationInformation.isUnexplored(state) && (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(state, explorationInformation)))) { relevantStates.push_back(state); } @@ -493,7 +502,7 @@ namespace storm { } template - void SparseMdpExplorationModelChecker::collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const { + void SparseMdpExplorationModelChecker::collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, Bounds& bounds) const { bool containsTargetState = false; // Now we record all actions leaving the EC. @@ -547,12 +556,12 @@ namespace storm { // Add to the new row group all leaving actions of contained states and set the appropriate bounds for // the actions and the new state. - std::pair stateBounds = getLowestBounds(explorationInformation.optimizationDirection); + std::pair stateBounds = getLowestBounds(explorationInformation.getOptimizationDirection()); for (auto const& action : leavingActions) { explorationInformation.matrix.emplace_back(std::move(explorationInformation.matrix[action])); std::pair const& actionBounds = bounds.getBoundsForAction(action); bounds.initializeBoundsForNextAction(actionBounds); - stateBounds = combineBounds(explorationInformation.optimizationDirection, stateBounds, actionBounds); + stateBounds = combineBounds(explorationInformation.getOptimizationDirection(), stateBounds, actionBounds); } bounds.setBoundsForRowGroup(nextRowGroup, stateBounds); @@ -562,7 +571,7 @@ namespace storm { } template - ValueType SparseMdpExplorationModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType SparseMdpExplorationModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { ValueType result = storm::utility::zero(); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { result += element.getValue() * bounds.getLowerBoundForState(element.getColumn(), explorationInformation); @@ -571,7 +580,7 @@ namespace storm { } template - ValueType SparseMdpExplorationModelChecker::computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + ValueType SparseMdpExplorationModelChecker::computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { ValueType result = storm::utility::zero(); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { result += element.getValue() * bounds.getUpperBoundForState(element.getColumn(), explorationInformation); @@ -580,7 +589,7 @@ namespace storm { } template - std::pair SparseMdpExplorationModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + std::pair SparseMdpExplorationModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { // TODO: take into account self-loops? std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { @@ -591,18 +600,18 @@ namespace storm { } template - std::pair SparseMdpExplorationModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { + std::pair SparseMdpExplorationModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { StateType group = explorationInformation.getRowGroup(currentStateId); - std::pair result = getLowestBounds(explorationInformation.optimizationDirection); + std::pair result = getLowestBounds(explorationInformation.getOptimizationDirection()); for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { std::pair actionValues = computeBoundsOfAction(action, explorationInformation, bounds); - result = combineBounds(explorationInformation.optimizationDirection, result, actionValues); + result = combineBounds(explorationInformation.getOptimizationDirection(), result, actionValues); } return result; } template - void SparseMdpExplorationModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + void SparseMdpExplorationModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, Bounds& bounds) const { stack.pop_back(); while (!stack.empty()) { updateProbabilityOfAction(stack.back().first, stack.back().second, explorationInformation, bounds); @@ -611,7 +620,7 @@ namespace storm { } template - void SparseMdpExplorationModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const { + void SparseMdpExplorationModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds& bounds) const { // Compute the new lower/upper values of the action. std::pair newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds); @@ -646,8 +655,8 @@ namespace storm { } template - ValueType SparseMdpExplorationModelChecker::computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const { - ValueType bound = getLowestBound(explorationInformation.optimizationDirection); + ValueType SparseMdpExplorationModelChecker::computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { + ValueType bound = getLowestBound(explorationInformation.getOptimizationDirection()); ActionType group = explorationInformation.getRowGroup(state); for (auto currentAction = explorationInformation.getStartRowOfGroup(group); currentAction < explorationInformation.getStartRowOfGroup(group + 1); ++currentAction) { diff --git a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h index f73f7b39f..74e78cf0e 100644 --- a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h +++ b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h @@ -6,44 +6,35 @@ #include "src/modelchecker/AbstractModelChecker.h" #include "src/storage/prism/Program.h" -#include "src/storage/sparse/StateStorage.h" -#include "src/generator/PrismNextStateGenerator.h" #include "src/generator/CompressedState.h" #include "src/generator/VariableInformation.h" -#include "src/settings/SettingsManager.h" -#include "src/settings/modules/ExplorationSettings.h" - -#include "src/utility/macros.h" #include "src/utility/ConstantsComparator.h" -#include "src/utility/constants.h" namespace storm { namespace storage { - namespace sparse { - template - class StateStorage; - } - class MaximalEndComponent; } - - namespace generator { - template - class PrismNextStateGenerator; + namespace prism { + class Program; } namespace modelchecker { + namespace exploration_detail { + template class StateGeneration; + template class ExplorationInformation; + template class Bounds; + template class Statistics; + } + + using namespace exploration_detail; template class SparseMdpExplorationModelChecker : public AbstractModelChecker { public: typedef uint32_t StateType; - typedef uint32_t ActionType; - typedef boost::container::flat_set StateSet; - typedef boost::container::flat_set ActionSet; - typedef std::shared_ptr ActionSetPointer; + typedef StateType ActionType; typedef std::vector> StateActionStack; SparseMdpExplorationModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions); @@ -53,278 +44,31 @@ namespace storm { virtual std::unique_ptr computeUntilProbabilities(CheckTask const& checkTask) override; private: - // A structure containing the data assembled during exploration. - struct ExplorationInformation { - ExplorationInformation(uint_fast64_t bitsPerBucket, ActionType const& unexploredMarker = std::numeric_limits::max()) : stateStorage(bitsPerBucket), unexploredMarker(unexploredMarker), localPrecomputation(false), numberOfExplorationStepsUntilPrecomputation(100000), numberOfSampledPathsUntilPrecomputation(), nextStateHeuristic(storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability) { - - storm::settings::modules::ExplorationSettings const& settings = storm::settings::explorationSettings(); - localPrecomputation = settings.isLocalPrecomputationSet(); - if (settings.isNumberOfSampledPathsUntilPrecomputationSet()) { - numberOfSampledPathsUntilPrecomputation = settings.getNumberOfSampledPathsUntilPrecomputation(); - } - - nextStateHeuristic = settings.getNextStateHeuristic(); - STORM_LOG_ASSERT(useDifferenceWeightedProbabilityHeuristic() || useProbabilityHeuristic(), "Illegal next-state heuristic."); - } - - storm::storage::sparse::StateStorage stateStorage; - - std::vector>> matrix; - std::vector rowGroupIndices; - - std::vector stateToRowGroupMapping; - StateType unexploredMarker; - std::unordered_map unexploredStates; - - storm::OptimizationDirection optimizationDirection; - StateSet terminalStates; - - bool localPrecomputation; - uint_fast64_t numberOfExplorationStepsUntilPrecomputation; - boost::optional numberOfSampledPathsUntilPrecomputation; - - storm::settings::modules::ExplorationSettings::NextStateHeuristic nextStateHeuristic; - - void setInitialStates(std::vector const& initialStates) { - stateStorage.initialStateIndices = initialStates; - } - - StateType getFirstInitialState() const { - return stateStorage.initialStateIndices.front(); - } - - std::size_t getNumberOfInitialStates() const { - return stateStorage.initialStateIndices.size(); - } - - void addUnexploredState(storm::generator::CompressedState const& compressedState) { - stateToRowGroupMapping.push_back(unexploredMarker); - unexploredStates[stateStorage.numberOfStates] = compressedState; - ++stateStorage.numberOfStates; - } - - void assignStateToRowGroup(StateType const& state, ActionType const& rowGroup) { - stateToRowGroupMapping[state] = rowGroup; - } - - StateType assignStateToNextRowGroup(StateType const& state) { - stateToRowGroupMapping[state] = rowGroupIndices.size() - 1; - return stateToRowGroupMapping[state]; - } - - StateType getNextRowGroup() const { - return rowGroupIndices.size() - 1; - } - - void newRowGroup(ActionType const& action) { - rowGroupIndices.push_back(action); - } - - void newRowGroup() { - newRowGroup(matrix.size()); - } - - std::size_t getNumberOfUnexploredStates() const { - return unexploredStates.size(); - } - - std::size_t getNumberOfDiscoveredStates() const { - return stateStorage.numberOfStates; - } - - StateType const& getRowGroup(StateType const& state) const { - return stateToRowGroupMapping[state]; - } - - StateType const& getUnexploredMarker() const { - return unexploredMarker; - } - - bool isUnexplored(StateType const& state) const { - return stateToRowGroupMapping[state] == unexploredMarker; - } - - bool isTerminal(StateType const& state) const { - return terminalStates.find(state) != terminalStates.end(); - } - - ActionType const& getStartRowOfGroup(StateType const& group) const { - return rowGroupIndices[group]; - } - - std::size_t getRowGroupSize(StateType const& group) const { - return rowGroupIndices[group + 1] - rowGroupIndices[group]; - } - - bool onlyOneActionAvailable(StateType const& group) const { - return getRowGroupSize(group) == 1; - } - - void addTerminalState(StateType const& state) { - terminalStates.insert(state); - } - - std::vector>& getRowOfMatrix(ActionType const& row) { - return matrix[row]; - } - - std::vector> const& getRowOfMatrix(ActionType const& row) const { - return matrix[row]; - } - - void addRowsToMatrix(std::size_t const& count) { - matrix.resize(matrix.size() + count); - } - - bool maximize() const { - return optimizationDirection == storm::OptimizationDirection::Maximize; - } - - bool minimize() const { - return !maximize(); - } - - bool performPrecomputationExcessiveExplorationSteps(std::size_t& numberExplorationStepsSinceLastPrecomputation) const { - bool result = numberExplorationStepsSinceLastPrecomputation > numberOfExplorationStepsUntilPrecomputation; - if (result) { - numberExplorationStepsSinceLastPrecomputation = 0; - } - return result; - } - - bool performPrecomputationExcessiveSampledPaths(std::size_t& numberOfSampledPathsSinceLastPrecomputation) const { - if (!numberOfSampledPathsUntilPrecomputation) { - return false; - } else { - bool result = numberOfSampledPathsSinceLastPrecomputation > numberOfSampledPathsUntilPrecomputation.get(); - if (result) { - numberOfSampledPathsSinceLastPrecomputation = 0; - } - return result; - } - } - - bool useLocalPrecomputation() const { - return localPrecomputation; - } - - bool useGlobalPrecomputation() const { - return !useLocalPrecomputation(); - } - - storm::settings::modules::ExplorationSettings::NextStateHeuristic const& getNextStateHeuristic() const { - return nextStateHeuristic; - } - - bool useDifferenceWeightedProbabilityHeuristic() const { - return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability; - } - - bool useProbabilityHeuristic() const { - return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::Probability; - } - }; - - // A struct that keeps track of certain statistics during the computation. - struct Statistics { - 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. - } - - void explorationStep() { - ++explorationSteps; - ++explorationStepsSinceLastPrecomputation; - } - - void sampledPath() { - ++pathsSampled; - ++pathsSampledSinceLastPrecomputation; - } - - void updateMaxPathLength(std::size_t const& currentPathLength) { - maxPathLength = std::max(maxPathLength, currentPathLength); - } - - 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; - - void printToStream(std::ostream& out, ExplorationInformation 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; - } - }; - - // A struct containing the data required for state exploration. - struct StateGeneration { - 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. - } - - void load(storm::generator::CompressedState const& state) { - generator.load(state); - } - - std::vector getInitialStates() { - return generator.getInitialStates(stateToIdCallback); - } - - storm::generator::StateBehavior expand() { - return generator.expand(stateToIdCallback); - } - - bool isConditionState() const { - return generator.satisfies(conditionStateExpression); - } - - bool isTargetState() const { - return generator.satisfies(targetStateExpression); - } - - storm::generator::PrismNextStateGenerator generator; - std::function stateToIdCallback; - storm::expressions::Expression conditionStateExpression; - storm::expressions::Expression targetStateExpression; - }; - - std::function createStateToIdCallback(ExplorationInformation& explorationInformation) const; + std::function createStateToIdCallback(ExplorationInformation& explorationInformation) const; - std::tuple performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const; + std::tuple performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const; - bool samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, BoundValues& bounds, Statistics& stats) const; + bool samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, Bounds& bounds, Statistics& stats) const; - bool exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; + bool exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const; - ActionType sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; + ActionType sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds& bounds) const; - StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + StateType sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, Bounds const& bounds) const; - bool performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, BoundValues& bounds, Statistics& stats) const; + bool performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const; - void collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, BoundValues& bounds) const; + void collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, Bounds& bounds) const; - void updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; + void updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, Bounds& bounds) const; - void updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues& bounds) const; + void updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds& bounds) const; - std::pair computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - ValueType computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - std::pair computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - ValueType computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; - ValueType computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, BoundValues const& bounds) const; + std::pair computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const; + ValueType computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const; + std::pair computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds const& bounds) const; + ValueType computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const; + ValueType computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const; std::pair getLowestBounds(storm::OptimizationDirection const& direction) const; ValueType getLowestBound(storm::OptimizationDirection const& direction) const; diff --git a/src/modelchecker/exploration/StateGeneration.cpp b/src/modelchecker/exploration/StateGeneration.cpp index e69de29bb..ae2343706 100644 --- a/src/modelchecker/exploration/StateGeneration.cpp +++ b/src/modelchecker/exploration/StateGeneration.cpp @@ -0,0 +1,45 @@ +#include "src/modelchecker/exploration/StateGeneration.h" + +namespace storm { + namespace modelchecker { + namespace exploration_detail { + + template + StateGeneration::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 + void StateGeneration::setStateToIdCallback(std::function const& stateToIdCallback) { + this->stateToIdCallback = stateToIdCallback; + } + + template + void StateGeneration::load(storm::generator::CompressedState const& state) { + generator.load(state); + } + + template + std::vector StateGeneration::getInitialStates() { + return generator.getInitialStates(stateToIdCallback); + } + + template + storm::generator::StateBehavior StateGeneration::expand() { + return generator.expand(stateToIdCallback); + } + + template + bool StateGeneration::isConditionState() const { + return generator.satisfies(conditionStateExpression); + } + + template + bool StateGeneration::isTargetState() const { + return generator.satisfies(targetStateExpression); + } + + template class StateGeneration; + } + } +} \ No newline at end of file diff --git a/src/modelchecker/exploration/StateGeneration.h b/src/modelchecker/exploration/StateGeneration.h index e69de29bb..a10ff2883 100644 --- a/src/modelchecker/exploration/StateGeneration.h +++ b/src/modelchecker/exploration/StateGeneration.h @@ -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 + class PrismNextStateGenerator; + } + + namespace modelchecker { + namespace exploration_detail { + + template + 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 const& stateToIdCallback); + + void load(storm::generator::CompressedState const& state); + + std::vector getInitialStates(); + + storm::generator::StateBehavior expand(); + + bool isConditionState() const; + + bool isTargetState() const; + + private: + storm::generator::PrismNextStateGenerator generator; + std::function stateToIdCallback; + storm::expressions::Expression conditionStateExpression; + storm::expressions::Expression targetStateExpression; + }; + + } + } +} + +#endif /* STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATEGENERATION_H_ */ \ No newline at end of file diff --git a/src/modelchecker/exploration/Statistics.cpp b/src/modelchecker/exploration/Statistics.cpp new file mode 100644 index 000000000..d74a62815 --- /dev/null +++ b/src/modelchecker/exploration/Statistics.cpp @@ -0,0 +1,46 @@ +#include "src/modelchecker/exploration/Statistics.h" + +#include "src/modelchecker/exploration/ExplorationInformation.h" + +namespace storm { + namespace modelchecker { + namespace exploration_detail { + + template + Statistics::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 + void Statistics::explorationStep() { + ++explorationSteps; + ++explorationStepsSinceLastPrecomputation; + } + + template + void Statistics::sampledPath() { + ++pathsSampled; + ++pathsSampledSinceLastPrecomputation; + } + + template + void Statistics::updateMaxPathLength(std::size_t const& currentPathLength) { + maxPathLength = std::max(maxPathLength, currentPathLength); + } + + template + void Statistics::printToStream(std::ostream& out, ExplorationInformation 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; + + } + } +} diff --git a/src/modelchecker/exploration/Statistics.h b/src/modelchecker/exploration/Statistics.h new file mode 100644 index 000000000..2d6b9d89f --- /dev/null +++ b/src/modelchecker/exploration/Statistics.h @@ -0,0 +1,44 @@ +#ifndef STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATISTICS_H_ +#define STORM_MODELCHECKER_EXPLORATION_EXPLORATION_DETAIL_STATISTICS_H_ + +#include +#include + +namespace storm { + namespace modelchecker { + namespace exploration_detail { + + template + class ExplorationInformation; + + // A struct that keeps track of certain statistics during the exploration. + template + struct Statistics { + Statistics(); + + void explorationStep(); + + void sampledPath(); + + void updateMaxPathLength(std::size_t const& currentPathLength); + + void printToStream(std::ostream& out, ExplorationInformation 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_ */ \ No newline at end of file From db3d1df863964cefc5bf4cad9c7593a332093974 Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 13 Apr 2016 22:53:00 +0200 Subject: [PATCH 28/31] added a sh*t ton of debug output, didn't help Former-commit-id: a1bf88c024dea214063d0f97089f0930e30a17e3 --- src/builder/ExplicitPrismModelBuilder.cpp | 7 +- src/modelchecker/exploration/Bounds.cpp | 13 ++- src/modelchecker/exploration/Bounds.h | 2 - .../exploration/ExplorationInformation.cpp | 38 +++++--- .../exploration/ExplorationInformation.h | 28 +++--- .../SparseMdpExplorationModelChecker.cpp | 86 ++++++++----------- .../SparseMdpExplorationModelChecker.h | 2 - .../exploration/StateGeneration.cpp | 42 +++++++-- .../exploration/StateGeneration.h | 20 ++++- src/settings/modules/ExplorationSettings.cpp | 4 +- src/storage/sparse/StateStorage.cpp | 7 +- src/storage/sparse/StateStorage.h | 2 +- 12 files changed, 147 insertions(+), 104 deletions(-) diff --git a/src/builder/ExplicitPrismModelBuilder.cpp b/src/builder/ExplicitPrismModelBuilder.cpp index ed65da9da..d5b8660b2 100644 --- a/src/builder/ExplicitPrismModelBuilder.cpp +++ b/src/builder/ExplicitPrismModelBuilder.cpp @@ -229,10 +229,10 @@ namespace storm { template StateType ExplicitPrismModelBuilder::getOrAddStateIndex(CompressedState const& state) { - uint32_t newIndex = stateStorage.numberOfStates; + StateType newIndex = static_cast(stateStorage.getNumberOfStates()); // Check, if the state was already registered. - std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); + std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); if (actualIndexBucketPair.first == newIndex) { if (options.explorationOrder == ExplorationOrder::Dfs) { @@ -245,7 +245,6 @@ namespace storm { } else { STORM_LOG_ASSERT(false, "Invalid exploration order."); } - ++stateStorage.numberOfStates; } return actualIndexBucketPair.first; @@ -467,7 +466,7 @@ namespace storm { // Finally -- if requested -- build the state information that can be retrieved from the outside. if (options.buildStateValuations) { - stateValuations = storm::storage::sparse::StateValuations(stateStorage.numberOfStates); + stateValuations = storm::storage::sparse::StateValuations(stateStorage.getNumberOfStates()); for (auto const& bitVectorIndexPair : stateStorage.stateToId) { stateValuations.get().valuations[bitVectorIndexPair.second] = unpackStateIntoValuation(bitVectorIndexPair.first, variableInformation, program.getManager()); } diff --git a/src/modelchecker/exploration/Bounds.cpp b/src/modelchecker/exploration/Bounds.cpp index 32ad0cb92..9892c33f0 100644 --- a/src/modelchecker/exploration/Bounds.cpp +++ b/src/modelchecker/exploration/Bounds.cpp @@ -10,18 +10,14 @@ namespace storm { std::pair Bounds::getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { ActionType index = explorationInformation.getRowGroup(state); if (index == explorationInformation.getUnexploredMarker()) { + std::cout << "state " << state << " is unexplored! retuning zero/one" << std::endl; return std::make_pair(storm::utility::zero(), storm::utility::one()); } else { + std::cout << "accessing at index " << index << " out of " << boundsPerState.size() << std::endl; return boundsPerState[index]; } } - - template - std::pair const& Bounds::getBoundsForExploredState(StateType const& state, ExplorationInformation const& explorationInformation) const { - ActionType index = explorationInformation.getRowGroup(state); - return boundsPerState[index]; - } - + template ValueType Bounds::getLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const { ActionType index = explorationInformation.getRowGroup(state); @@ -78,7 +74,7 @@ namespace storm { template ValueType Bounds::getDifferenceOfStateBounds(StateType const& state, ExplorationInformation const& explorationInformation) const { - std::pair const& bounds = getBoundsForExploredState(state, explorationInformation); + std::pair bounds = getBoundsForState(state, explorationInformation); return bounds.second - bounds.first; } @@ -120,6 +116,7 @@ namespace storm { template void Bounds::setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { StateType const& rowGroup = explorationInformation.getRowGroup(state); + std::cout << "setting " << values.first << ", " << values.second << " for state " << state << std::endl; setBoundsForRowGroup(rowGroup, values); } diff --git a/src/modelchecker/exploration/Bounds.h b/src/modelchecker/exploration/Bounds.h index 27e0979a5..816e8c923 100644 --- a/src/modelchecker/exploration/Bounds.h +++ b/src/modelchecker/exploration/Bounds.h @@ -21,8 +21,6 @@ namespace storm { typedef StateType ActionType; std::pair getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const; - - std::pair const& getBoundsForExploredState(StateType const& state, ExplorationInformation const& explorationInformation) const; ValueType getLowerBoundForState(StateType const& state, ExplorationInformation const& explorationInformation) const; diff --git a/src/modelchecker/exploration/ExplorationInformation.cpp b/src/modelchecker/exploration/ExplorationInformation.cpp index 12715f307..7f45eb6c6 100644 --- a/src/modelchecker/exploration/ExplorationInformation.cpp +++ b/src/modelchecker/exploration/ExplorationInformation.cpp @@ -10,7 +10,7 @@ namespace storm { namespace exploration_detail { template - ExplorationInformation::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) { + ExplorationInformation::ExplorationInformation(storm::OptimizationDirection const& direction, ActionType const& unexploredMarker) : 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(); @@ -24,25 +24,24 @@ namespace storm { } template - void ExplorationInformation::setInitialStates(std::vector const& initialStates) { - stateStorage.initialStateIndices = initialStates; + typename ExplorationInformation::const_iterator ExplorationInformation::findUnexploredState(StateType const& state) const { + return unexploredStates.find(state); } template - StateType ExplorationInformation::getFirstInitialState() const { - return stateStorage.initialStateIndices.front(); + typename ExplorationInformation::const_iterator ExplorationInformation::unexploredStatesEnd() const { + return unexploredStates.end(); } template - std::size_t ExplorationInformation::getNumberOfInitialStates() const { - return stateStorage.initialStateIndices.size(); + void ExplorationInformation::removeUnexploredState(const_iterator it) { + unexploredStates.erase(it); } template - void ExplorationInformation::addUnexploredState(storm::generator::CompressedState const& compressedState) { + void ExplorationInformation::addUnexploredState(StateType const& stateId, storm::generator::CompressedState const& compressedState) { stateToRowGroupMapping.push_back(unexploredMarker); - unexploredStates[stateStorage.numberOfStates] = compressedState; - ++stateStorage.numberOfStates; + unexploredStates[stateId] = compressedState; } template @@ -71,6 +70,21 @@ namespace storm { newRowGroup(matrix.size()); } + template + void ExplorationInformation::terminateCurrentRowGroup() { + rowGroupIndices.push_back(matrix.size()); + } + + template + void ExplorationInformation::moveActionToBackOfMatrix(ActionType const& action) { + matrix.emplace_back(std::move(matrix[action])); + } + + template + StateType ExplorationInformation::getActionCount() const { + return matrix.size(); + } + template std::size_t ExplorationInformation::getNumberOfUnexploredStates() const { return unexploredStates.size(); @@ -78,7 +92,7 @@ namespace storm { template std::size_t ExplorationInformation::getNumberOfDiscoveredStates() const { - return stateStorage.numberOfStates; + return stateToRowGroupMapping.size(); } template @@ -132,7 +146,7 @@ namespace storm { } template - void ExplorationInformation::addRowsToMatrix(std::size_t const& count) { + void ExplorationInformation::addActionsToMatrix(std::size_t const& count) { matrix.resize(matrix.size() + count); } diff --git a/src/modelchecker/exploration/ExplorationInformation.h b/src/modelchecker/exploration/ExplorationInformation.h index b073352cd..550b3d32c 100644 --- a/src/modelchecker/exploration/ExplorationInformation.h +++ b/src/modelchecker/exploration/ExplorationInformation.h @@ -13,7 +13,6 @@ #include "src/generator/CompressedState.h" #include "src/storage/SparseMatrix.h" -#include "src/storage/sparse/StateStorage.h" #include "src/settings/modules/ExplorationSettings.h" @@ -25,16 +24,19 @@ namespace storm { public: typedef StateType ActionType; typedef boost::container::flat_set StateSet; + typedef std::unordered_map IdToStateMap; + typedef typename IdToStateMap::const_iterator const_iterator; + typedef std::vector>> MatrixType; - ExplorationInformation(uint_fast64_t bitsPerBucket, storm::OptimizationDirection const& direction, ActionType const& unexploredMarker = std::numeric_limits::max()); + ExplorationInformation(storm::OptimizationDirection const& direction, ActionType const& unexploredMarker = std::numeric_limits::max()); - void setInitialStates(std::vector const& initialStates); + const_iterator findUnexploredState(StateType const& state) const; - StateType getFirstInitialState() const; + const_iterator unexploredStatesEnd() const; - std::size_t getNumberOfInitialStates() const; + void removeUnexploredState(const_iterator it); - void addUnexploredState(storm::generator::CompressedState const& compressedState); + void addUnexploredState(StateType const& stateId, storm::generator::CompressedState const& compressedState); void assignStateToRowGroup(StateType const& state, ActionType const& rowGroup); @@ -46,6 +48,12 @@ namespace storm { void newRowGroup(); + void terminateCurrentRowGroup(); + + void moveActionToBackOfMatrix(ActionType const& action); + + StateType getActionCount() const; + std::size_t getNumberOfUnexploredStates() const; std::size_t getNumberOfDiscoveredStates() const; @@ -70,7 +78,7 @@ namespace storm { std::vector> const& getRowOfMatrix(ActionType const& row) const; - void addRowsToMatrix(std::size_t const& count); + void addActionsToMatrix(std::size_t const& count); bool maximize() const; @@ -95,14 +103,12 @@ namespace storm { void setOptimizationDirection(storm::OptimizationDirection const& direction); private: - storm::storage::sparse::StateStorage stateStorage; - - std::vector>> matrix; + MatrixType matrix; std::vector rowGroupIndices; std::vector stateToRowGroupMapping; StateType unexploredMarker; - std::unordered_map unexploredStates; + IdToStateMap unexploredStates; storm::OptimizationDirection optimizationDirection; StateSet terminalStates; diff --git a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp index 4ad5b5613..9fd4c85f8 100644 --- a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp +++ b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp @@ -52,45 +52,26 @@ namespace storm { storm::logic::Formula const& targetFormula = untilFormula.getRightSubformula(); STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "For nondeterministic systems, an optimization direction (min/max) must be given in the property."); - std::map labelToExpressionMapping = program.getLabelToExpressionMapping(); - StateGeneration stateGeneration(program, variableInformation, conditionFormula.toExpression(program.getManager(), labelToExpressionMapping), targetFormula.toExpression(program.getManager(), labelToExpressionMapping)); - - ExplorationInformation explorationInformation(variableInformation.getTotalBitOffset(true), checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize); - + ExplorationInformation explorationInformation(checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize); + // The first row group starts at action 0. explorationInformation.newRowGroup(0); - // Create a callback for the next-state generator to enable it to request the index of states. - stateGeneration.setStateToIdCallback(createStateToIdCallback(explorationInformation)); + std::map labelToExpressionMapping = program.getLabelToExpressionMapping(); + StateGeneration stateGeneration(program, variableInformation, explorationInformation, conditionFormula.toExpression(program.getManager(), labelToExpressionMapping), targetFormula.toExpression(program.getManager(), labelToExpressionMapping)); + // Compute and return result. std::tuple boundsForInitialState = performExploration(stateGeneration, explorationInformation); return std::make_unique>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); } - - template - std::function::StateType (storm::generator::CompressedState const&)> SparseMdpExplorationModelChecker::createStateToIdCallback(ExplorationInformation& explorationInformation) const { - return [&explorationInformation,this] (storm::generator::CompressedState const& state) -> StateType { - StateType newIndex = explorationInformation.getNumberOfDiscoveredStates(); - // Check, if the state was already registered. - std::pair actualIndexBucketPair = explorationInformation.stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); - - if (actualIndexBucketPair.first == newIndex) { - explorationInformation.addUnexploredState(state); - } - - return actualIndexBucketPair.first; - }; - } - template std::tuple::StateType, ValueType, ValueType> SparseMdpExplorationModelChecker::performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { - // Generate the initial state so we know where to start the simulation. - explorationInformation.setInitialStates(stateGeneration.getInitialStates()); - STORM_LOG_THROW(explorationInformation.getNumberOfInitialStates() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the exploration engine."); - StateType initialStateIndex = explorationInformation.getFirstInitialState(); + stateGeneration.computeInitialStates(); + STORM_LOG_THROW(stateGeneration.getNumberOfInitialStates() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the exploration engine."); + StateType initialStateIndex = stateGeneration.getFirstInitialState(); // Create a structure that holds the bounds for the states and actions. Bounds bounds; @@ -141,7 +122,7 @@ namespace storm { template bool SparseMdpExplorationModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, Bounds& bounds, Statistics& stats) const { // Start the search from the initial state. - stack.push_back(std::make_pair(explorationInformation.getFirstInitialState(), 0)); + stack.push_back(std::make_pair(stateGeneration.getFirstInitialState(), 0)); // As long as we didn't find a terminal (accepting or rejecting) state in the search, sample a new successor. bool foundTerminalState = false; @@ -150,8 +131,8 @@ namespace storm { STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); // If the state is not yet explored, we need to retrieve its behaviors. - auto unexploredIt = explorationInformation.unexploredStates.find(currentStateId); - if (unexploredIt != explorationInformation.unexploredStates.end()) { + auto unexploredIt = explorationInformation.findUnexploredState(currentStateId); + if (unexploredIt != explorationInformation.unexploredStatesEnd()) { STORM_LOG_TRACE("State was not yet explored."); // Explore the previously unexplored state. @@ -160,7 +141,7 @@ namespace storm { if (foundTerminalState) { STORM_LOG_TRACE("Aborting sampling of path, because a terminal state was reached."); } - explorationInformation.unexploredStates.erase(unexploredIt); + explorationInformation.removeUnexploredState(unexploredIt); } else { // If the state was already explored, we check whether it is a terminal state or not. if (explorationInformation.isTerminal(currentStateId)) { @@ -245,30 +226,31 @@ namespace storm { // need to store its behavior. if (!isTerminalState) { // Next, we insert the behavior into our matrix structure. - StateType startRow = explorationInformation.matrix.size(); - explorationInformation.addRowsToMatrix(behavior.getNumberOfChoices()); + StateType startAction = explorationInformation.getActionCount(); + explorationInformation.addActionsToMatrix(behavior.getNumberOfChoices()); - ActionType currentAction = 0; + ActionType localAction = 0; // Retrieve the lowest state bounds (wrt. to the current optimization direction). std::pair stateBounds = getLowestBounds(explorationInformation.getOptimizationDirection()); for (auto const& choice : behavior) { for (auto const& entry : choice) { - explorationInformation.getRowOfMatrix(startRow + currentAction).emplace_back(entry.first, entry.second); + explorationInformation.getRowOfMatrix(startAction + localAction).emplace_back(entry.first, entry.second); + std::cout << "adding " << (startAction + localAction) << "x" << entry.first << " -> " << entry.second << std::endl; } - std::pair actionBounds = computeBoundsOfAction(startRow + currentAction, explorationInformation, bounds); + std::pair actionBounds = computeBoundsOfAction(startAction + localAction, explorationInformation, bounds); bounds.initializeBoundsForNextAction(actionBounds); stateBounds = combineBounds(explorationInformation.getOptimizationDirection(), stateBounds, actionBounds); - STORM_LOG_TRACE("Initializing bounds of action " << (startRow + currentAction) << " to " << bounds.getLowerBoundForAction(startRow + currentAction) << " and " << bounds.getUpperBoundForAction(startRow + currentAction) << "."); + STORM_LOG_TRACE("Initializing bounds of action " << (startAction + localAction) << " to " << bounds.getLowerBoundForAction(startAction + localAction) << " and " << bounds.getUpperBoundForAction(startAction + localAction) << "."); - ++currentAction; + ++localAction; } // Terminate the row group. - explorationInformation.rowGroupIndices.push_back(explorationInformation.matrix.size()); + explorationInformation.terminateCurrentRowGroup(); bounds.setBoundsForState(currentStateId, explorationInformation, stateBounds); STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << bounds.getLowerBoundForState(currentStateId, explorationInformation) << " and " << bounds.getUpperBoundForState(currentStateId, explorationInformation) << "."); @@ -292,7 +274,7 @@ namespace storm { } // Increase the size of the matrix, but leave the row empty. - explorationInformation.addRowsToMatrix(1); + explorationInformation.addActionsToMatrix(1); // Terminate the row group. explorationInformation.newRowGroup(); @@ -342,9 +324,9 @@ namespace storm { template typename SparseMdpExplorationModelChecker::StateType SparseMdpExplorationModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { std::vector> const& row = explorationInformation.getRowOfMatrix(chosenAction); -// if (row.size() == 1) { -// return row.front().getColumn(); -// } + if (row.size() == 1) { + return row.front().getColumn(); + } std::vector probabilities(row.size()); @@ -352,6 +334,7 @@ namespace storm { if (explorationInformation.useDifferenceWeightedProbabilityHeuristic()) { std::transform(row.begin(), row.end(), probabilities.begin(), [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { + std::cout << entry.getColumn() << " with diff " << bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation) << std::endl; return entry.getValue() * bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation); }); } else if (explorationInformation.useProbabilityHeuristic()) { @@ -393,9 +376,8 @@ namespace storm { relevantStates.resize(std::distance(relevantStates.begin(), newEnd)); } else { for (StateType state = 0; state < explorationInformation.getNumberOfDiscoveredStates(); ++state) { - // Add the state to the relevant states if they are not unexplored. Additionally, if we are - // computing minimal probabilities, we only consider it relevant if it's not a target state. - if (!explorationInformation.isUnexplored(state) && (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(state, explorationInformation)))) { + // Add the state to the relevant states if they are not unexplored. + if (!explorationInformation.isUnexplored(state)) { relevantStates.push_back(state); } } @@ -408,8 +390,10 @@ namespace storm { storm::storage::BitVector targetStates(sink + 1); for (StateType index = 0; index < relevantStates.size(); ++index) { relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); + std::cout << "lower bound for state " << relevantStates[index] << " is " << bounds.getLowerBoundForState(relevantStates[index], explorationInformation) << std::endl; if (storm::utility::isOne(bounds.getLowerBoundForState(relevantStates[index], explorationInformation))) { targetStates.set(index); + std::cout << relevantStates[index] << " was identified as a target state" << std::endl; } } @@ -425,9 +409,11 @@ namespace storm { if (it != relevantStateToNewRowGroupMapping.end()) { // If the entry is a relevant state, we copy it over (and compensate for the offset change). builder.addNextValue(currentRow, it->second, entry.getValue()); + std::cout << state << " to " << entry.getColumn() << " with prob " << entry.getValue() << std::endl; } else { // If the entry is an unexpanded state, we gather the probability to later redirect it to an unexpanded sink. unexpandedProbability += entry.getValue(); + std::cout << state << " has unexpanded prob " << unexpandedProbability << " to succ " << entry.getColumn() << std::endl; } } if (unexpandedProbability != storm::utility::zero()) { @@ -487,9 +473,13 @@ namespace storm { statesWithProbability1 = storm::utility::graph::performProb1A(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); } + std::cout << statesWithProbability0 << std::endl; + std::cout << statesWithProbability1 << std::endl; + // Set the bounds of the identified states. for (auto state : statesWithProbability0) { StateType originalState = relevantStates[state]; + std::cout << "determined " << originalState << " as a prob0 state" << std::endl; bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); explorationInformation.addTerminalState(originalState); } @@ -558,7 +548,7 @@ namespace storm { // the actions and the new state. std::pair stateBounds = getLowestBounds(explorationInformation.getOptimizationDirection()); for (auto const& action : leavingActions) { - explorationInformation.matrix.emplace_back(std::move(explorationInformation.matrix[action])); + explorationInformation.moveActionToBackOfMatrix(action); std::pair const& actionBounds = bounds.getBoundsForAction(action); bounds.initializeBoundsForNextAction(actionBounds); stateBounds = combineBounds(explorationInformation.getOptimizationDirection(), stateBounds, actionBounds); @@ -566,7 +556,7 @@ namespace storm { bounds.setBoundsForRowGroup(nextRowGroup, stateBounds); // Terminate the row group of the newly introduced state. - explorationInformation.rowGroupIndices.push_back(explorationInformation.matrix.size()); + explorationInformation.terminateCurrentRowGroup(); } } diff --git a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h index 74e78cf0e..5db171722 100644 --- a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h +++ b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h @@ -44,8 +44,6 @@ namespace storm { virtual std::unique_ptr computeUntilProbabilities(CheckTask const& checkTask) override; private: - std::function createStateToIdCallback(ExplorationInformation& explorationInformation) const; - std::tuple performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const; bool samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, Bounds& bounds, Statistics& stats) const; diff --git a/src/modelchecker/exploration/StateGeneration.cpp b/src/modelchecker/exploration/StateGeneration.cpp index ae2343706..71fc4d416 100644 --- a/src/modelchecker/exploration/StateGeneration.cpp +++ b/src/modelchecker/exploration/StateGeneration.cpp @@ -1,17 +1,26 @@ #include "src/modelchecker/exploration/StateGeneration.h" +#include "src/modelchecker/exploration/ExplorationInformation.h" + namespace storm { namespace modelchecker { namespace exploration_detail { template - StateGeneration::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 - void StateGeneration::setStateToIdCallback(std::function const& stateToIdCallback) { - this->stateToIdCallback = stateToIdCallback; + StateGeneration::StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, ExplorationInformation& explorationInformation, storm::expressions::Expression const& conditionStateExpression, storm::expressions::Expression const& targetStateExpression) : generator(program, variableInformation, false), stateStorage(variableInformation.getTotalBitOffset(true)), conditionStateExpression(conditionStateExpression), targetStateExpression(targetStateExpression) { + + stateToIdCallback = [&explorationInformation, this] (storm::generator::CompressedState const& state) -> StateType { + StateType newIndex = stateStorage.getNumberOfStates(); + + // Check, if the state was already registered. + std::pair actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); + + if (actualIndexBucketPair.first == newIndex) { + explorationInformation.addUnexploredState(newIndex, state); + } + + return actualIndexBucketPair.first; + }; } template @@ -21,7 +30,7 @@ namespace storm { template std::vector StateGeneration::getInitialStates() { - return generator.getInitialStates(stateToIdCallback); + return stateStorage.initialStateIndices; } template @@ -38,7 +47,22 @@ namespace storm { bool StateGeneration::isTargetState() const { return generator.satisfies(targetStateExpression); } - + + template + void StateGeneration::computeInitialStates() { + stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback); + } + + template + StateType StateGeneration::getFirstInitialState() const { + return stateStorage.initialStateIndices.front(); + } + + template + std::size_t StateGeneration::getNumberOfInitialStates() const { + return stateStorage.initialStateIndices.size(); + } + template class StateGeneration; } } diff --git a/src/modelchecker/exploration/StateGeneration.h b/src/modelchecker/exploration/StateGeneration.h index a10ff2883..f75211e54 100644 --- a/src/modelchecker/exploration/StateGeneration.h +++ b/src/modelchecker/exploration/StateGeneration.h @@ -4,6 +4,8 @@ #include "src/generator/CompressedState.h" #include "src/generator/PrismNextStateGenerator.h" +#include "src/storage/sparse/StateStorage.h" + namespace storm { namespace generator { template @@ -13,19 +15,26 @@ namespace storm { namespace modelchecker { namespace exploration_detail { + template + class ExplorationInformation; + template 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 const& stateToIdCallback); - + StateGeneration(storm::prism::Program const& program, storm::generator::VariableInformation const& variableInformation, ExplorationInformation& explorationInformation, storm::expressions::Expression const& conditionStateExpression, storm::expressions::Expression const& targetStateExpression); + void load(storm::generator::CompressedState const& state); std::vector getInitialStates(); storm::generator::StateBehavior expand(); + void computeInitialStates(); + + StateType getFirstInitialState() const; + + std::size_t getNumberOfInitialStates() const; + bool isConditionState() const; bool isTargetState() const; @@ -33,6 +42,9 @@ namespace storm { private: storm::generator::PrismNextStateGenerator generator; std::function stateToIdCallback; + + storm::storage::sparse::StateStorage stateStorage; + storm::expressions::Expression conditionStateExpression; storm::expressions::Expression targetStateExpression; }; diff --git a/src/settings/modules/ExplorationSettings.cpp b/src/settings/modules/ExplorationSettings.cpp index a890a1f9e..4a3a2796d 100644 --- a/src/settings/modules/ExplorationSettings.cpp +++ b/src/settings/modules/ExplorationSettings.cpp @@ -19,8 +19,8 @@ namespace storm { ExplorationSettings::ExplorationSettings(storm::settings::SettingsManager& settingsManager) : ModuleSettings(settingsManager, moduleName) { std::vector types = { "local", "global" }; this->addOption(storm::settings::OptionBuilder(moduleName, precomputationTypeOptionName, true, "Sets the kind of precomputation used. Available are: { local, global }.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(types)).setDefaultValueString("global").build()).build()); - this->addOption(storm::settings::OptionBuilder(moduleName, numberOfExplorationStepsUntilPrecomputationOptionName, false, "Sets the number of exploration steps to perform until a precomputation is triggered.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of exploration steps to perform.").setDefaultValueUnsignedInteger(100000).build()).build()); - this->addOption(storm::settings::OptionBuilder(moduleName, numberOfSampledPathsUntilPrecomputationOptionName, false, "If set, a precomputation is perfomed periodically after the given number of paths has been sampled.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of paths to sample until a precomputation is triggered.").setDefaultValueUnsignedInteger(100000).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, numberOfExplorationStepsUntilPrecomputationOptionName, true, "Sets the number of exploration steps to perform until a precomputation is triggered.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of exploration steps to perform.").setDefaultValueUnsignedInteger(100000).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, numberOfSampledPathsUntilPrecomputationOptionName, true, "If set, a precomputation is perfomed periodically after the given number of paths has been sampled.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of paths to sample until a precomputation is triggered.").setDefaultValueUnsignedInteger(100000).build()).build()); std::vector nextStateHeuristics = { "probdiff", "prob" }; this->addOption(storm::settings::OptionBuilder(moduleName, nextStateHeuristicOptionName, true, "Sets the next-state heuristic to use. Available are: { probdiff, prob } where 'prob' samples according to the probabilities in the system and 'probdiff' weights the probabilities with the differences between the current bounds.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the heuristic to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(nextStateHeuristics)).setDefaultValueString("probdiff").build()).build()); diff --git a/src/storage/sparse/StateStorage.cpp b/src/storage/sparse/StateStorage.cpp index 30c0eaace..60af49179 100644 --- a/src/storage/sparse/StateStorage.cpp +++ b/src/storage/sparse/StateStorage.cpp @@ -5,10 +5,15 @@ namespace storm { namespace sparse { template - StateStorage::StateStorage(uint64_t bitsPerState) : stateToId(bitsPerState, 10000000), initialStateIndices(), bitsPerState(bitsPerState), numberOfStates() { + StateStorage::StateStorage(uint64_t bitsPerState) : stateToId(bitsPerState, 10000000), initialStateIndices(), bitsPerState(bitsPerState) { // Intentionally left empty. } + template + uint_fast64_t StateStorage::getNumberOfStates() const { + return stateToId.size(); + } + template class StateStorage; } } diff --git a/src/storage/sparse/StateStorage.h b/src/storage/sparse/StateStorage.h index 79c8483ab..eee1855c0 100644 --- a/src/storage/sparse/StateStorage.h +++ b/src/storage/sparse/StateStorage.h @@ -25,7 +25,7 @@ namespace storm { uint64_t bitsPerState; // The number of states that were found in the exploration so far. - uint_fast64_t numberOfStates; + uint_fast64_t getNumberOfStates() const; }; } From 313edf44e1b67a37dded8fc66fcbf9e5a258fe83 Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 14 Apr 2016 10:58:34 +0200 Subject: [PATCH 29/31] added new uniform heuristic and changed probdiff to be the sum instead of product Former-commit-id: 6fcb9ad80b89086d59684486f0bd4f162cb3ebce --- src/modelchecker/exploration/Bounds.cpp | 3 - .../exploration/ExplorationInformation.cpp | 12 ++-- .../exploration/ExplorationInformation.h | 4 +- .../SparseMdpExplorationModelChecker.cpp | 63 +++++++++---------- src/settings/modules/ExplorationSettings.cpp | 10 +-- src/settings/modules/ExplorationSettings.h | 2 +- 6 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/modelchecker/exploration/Bounds.cpp b/src/modelchecker/exploration/Bounds.cpp index 9892c33f0..db36d0f16 100644 --- a/src/modelchecker/exploration/Bounds.cpp +++ b/src/modelchecker/exploration/Bounds.cpp @@ -10,10 +10,8 @@ namespace storm { std::pair Bounds::getBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation) const { ActionType index = explorationInformation.getRowGroup(state); if (index == explorationInformation.getUnexploredMarker()) { - std::cout << "state " << state << " is unexplored! retuning zero/one" << std::endl; return std::make_pair(storm::utility::zero(), storm::utility::one()); } else { - std::cout << "accessing at index " << index << " out of " << boundsPerState.size() << std::endl; return boundsPerState[index]; } } @@ -116,7 +114,6 @@ namespace storm { template void Bounds::setBoundsForState(StateType const& state, ExplorationInformation const& explorationInformation, std::pair const& values) { StateType const& rowGroup = explorationInformation.getRowGroup(state); - std::cout << "setting " << values.first << ", " << values.second << " for state " << state << std::endl; setBoundsForRowGroup(rowGroup, values); } diff --git a/src/modelchecker/exploration/ExplorationInformation.cpp b/src/modelchecker/exploration/ExplorationInformation.cpp index 7f45eb6c6..756fd57d9 100644 --- a/src/modelchecker/exploration/ExplorationInformation.cpp +++ b/src/modelchecker/exploration/ExplorationInformation.cpp @@ -10,7 +10,7 @@ namespace storm { namespace exploration_detail { template - ExplorationInformation::ExplorationInformation(storm::OptimizationDirection const& direction, ActionType const& unexploredMarker) : unexploredMarker(unexploredMarker), optimizationDirection(direction), localPrecomputation(false), numberOfExplorationStepsUntilPrecomputation(100000), numberOfSampledPathsUntilPrecomputation(), nextStateHeuristic(storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability) { + ExplorationInformation::ExplorationInformation(storm::OptimizationDirection const& direction, ActionType const& unexploredMarker) : unexploredMarker(unexploredMarker), optimizationDirection(direction), localPrecomputation(false), numberOfExplorationStepsUntilPrecomputation(100000), numberOfSampledPathsUntilPrecomputation(), nextStateHeuristic(storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceProbabilitySum) { storm::settings::modules::ExplorationSettings const& settings = storm::settings::explorationSettings(); localPrecomputation = settings.isLocalPrecomputationSet(); @@ -20,7 +20,6 @@ namespace storm { } nextStateHeuristic = settings.getNextStateHeuristic(); - STORM_LOG_ASSERT(useDifferenceWeightedProbabilityHeuristic() || useProbabilityHeuristic(), "Illegal next-state heuristic."); } template @@ -198,14 +197,19 @@ namespace storm { } template - bool ExplorationInformation::useDifferenceWeightedProbabilityHeuristic() const { - return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability; + bool ExplorationInformation::useDifferenceProbabilitySumHeuristic() const { + return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::DifferenceProbabilitySum; } template bool ExplorationInformation::useProbabilityHeuristic() const { return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::Probability; } + + template + bool ExplorationInformation::useUniformHeuristic() const { + return nextStateHeuristic == storm::settings::modules::ExplorationSettings::NextStateHeuristic::Uniform; + } template storm::OptimizationDirection const& ExplorationInformation::getOptimizationDirection() const { diff --git a/src/modelchecker/exploration/ExplorationInformation.h b/src/modelchecker/exploration/ExplorationInformation.h index 550b3d32c..4716bfc96 100644 --- a/src/modelchecker/exploration/ExplorationInformation.h +++ b/src/modelchecker/exploration/ExplorationInformation.h @@ -94,10 +94,12 @@ namespace storm { storm::settings::modules::ExplorationSettings::NextStateHeuristic const& getNextStateHeuristic() const; - bool useDifferenceWeightedProbabilityHeuristic() const; + bool useDifferenceProbabilitySumHeuristic() const; bool useProbabilityHeuristic() const; + bool useUniformHeuristic() const; + storm::OptimizationDirection const& getOptimizationDirection() const; void setOptimizationDirection(storm::OptimizationDirection const& direction); diff --git a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp index 9fd4c85f8..542273713 100644 --- a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp +++ b/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp @@ -53,7 +53,7 @@ namespace storm { STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "For nondeterministic systems, an optimization direction (min/max) must be given in the property."); ExplorationInformation explorationInformation(checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize); - + // The first row group starts at action 0. explorationInformation.newRowGroup(0); @@ -65,7 +65,7 @@ namespace storm { std::tuple boundsForInitialState = performExploration(stateGeneration, explorationInformation); return std::make_unique>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); } - + template std::tuple::StateType, ValueType, ValueType> SparseMdpExplorationModelChecker::performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { // Generate the initial state so we know where to start the simulation. @@ -75,7 +75,7 @@ namespace storm { // Create a structure that holds the bounds for the states and actions. Bounds bounds; - + // Create a stack that is used to track the path we sampled. StateActionStack stack; @@ -237,13 +237,13 @@ namespace storm { for (auto const& choice : behavior) { for (auto const& entry : choice) { explorationInformation.getRowOfMatrix(startAction + localAction).emplace_back(entry.first, entry.second); - std::cout << "adding " << (startAction + localAction) << "x" << entry.first << " -> " << entry.second << std::endl; + STORM_LOG_TRACE("Found transition " << currentStateId << "-[" << (startAction + localAction) << ", " << entry.second << "]-> " << entry.first << "."); } std::pair actionBounds = computeBoundsOfAction(startAction + localAction, explorationInformation, bounds); bounds.initializeBoundsForNextAction(actionBounds); stateBounds = combineBounds(explorationInformation.getOptimizationDirection(), stateBounds, actionBounds); - + STORM_LOG_TRACE("Initializing bounds of action " << (startAction + localAction) << " to " << bounds.getLowerBoundForAction(startAction + localAction) << " and " << bounds.getUpperBoundForAction(startAction + localAction) << "."); ++localAction; @@ -260,7 +260,7 @@ namespace storm { // terminal state. isTerminalState = true; } - + if (isTerminalState) { STORM_LOG_TRACE("State does not need to be explored, because it is " << (isTargetState ? "a target state" : "a rejecting terminal state") << "."); explorationInformation.addTerminalState(currentStateId); @@ -328,26 +328,29 @@ namespace storm { return row.front().getColumn(); } - std::vector probabilities(row.size()); // Depending on the selected next-state heuristic, we give the states other likelihoods of getting chosen. - if (explorationInformation.useDifferenceWeightedProbabilityHeuristic()) { + if (explorationInformation.useDifferenceProbabilitySumHeuristic() || explorationInformation.useProbabilityHeuristic()) { + std::vector probabilities(row.size()); + if (explorationInformation.useDifferenceProbabilitySumHeuristic()) { std::transform(row.begin(), row.end(), probabilities.begin(), [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { - std::cout << entry.getColumn() << " with diff " << bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation) << std::endl; - return entry.getValue() * bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation); - }); - } else if (explorationInformation.useProbabilityHeuristic()) { - std::transform(row.begin(), row.end(), probabilities.begin(), - [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { - return entry.getValue(); + return entry.getValue() + bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation); }); + } else if (explorationInformation.useProbabilityHeuristic()) { + std::transform(row.begin(), row.end(), probabilities.begin(), + [&bounds, &explorationInformation] (storm::storage::MatrixEntry const& entry) { + return entry.getValue(); + }); + } + + // Now sample according to the probabilities. + std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); + return row[distribution(randomGenerator)].getColumn(); + } else if (explorationInformation.useUniformHeuristic()) { + std::uniform_int_distribution distribution(0, row.size() - 1); + return row[distribution(randomGenerator)].getColumn(); } - - // Now sample according to the probabilities. - std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); - StateType offset = distribution(randomGenerator); - return row[offset].getColumn(); } template @@ -359,7 +362,7 @@ namespace storm { // 2. use this matrix to compute states with probability 0/1 and an MEC decomposition (in the max case). // 3. use MEC decomposition to collapse MECs. STORM_LOG_TRACE("Starting " << (explorationInformation.useLocalPrecomputation() ? "local" : "global") << " precomputation."); - + // Construct the matrix that represents the fragment of the system contained in the currently sampled path. storm::storage::SparseMatrixBuilder builder(0, 0, 0, false, true, 0); @@ -390,10 +393,8 @@ namespace storm { storm::storage::BitVector targetStates(sink + 1); for (StateType index = 0; index < relevantStates.size(); ++index) { relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); - std::cout << "lower bound for state " << relevantStates[index] << " is " << bounds.getLowerBoundForState(relevantStates[index], explorationInformation) << std::endl; if (storm::utility::isOne(bounds.getLowerBoundForState(relevantStates[index], explorationInformation))) { targetStates.set(index); - std::cout << relevantStates[index] << " was identified as a target state" << std::endl; } } @@ -409,11 +410,9 @@ namespace storm { if (it != relevantStateToNewRowGroupMapping.end()) { // If the entry is a relevant state, we copy it over (and compensate for the offset change). builder.addNextValue(currentRow, it->second, entry.getValue()); - std::cout << state << " to " << entry.getColumn() << " with prob " << entry.getValue() << std::endl; } else { // If the entry is an unexpanded state, we gather the probability to later redirect it to an unexpanded sink. unexpandedProbability += entry.getValue(); - std::cout << state << " has unexpanded prob " << unexpandedProbability << " to succ " << entry.getColumn() << std::endl; } } if (unexpandedProbability != storm::utility::zero()) { @@ -441,11 +440,11 @@ namespace storm { statesWithProbability0 = storm::utility::graph::performProb0A(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); targetStates.set(sink, true); statesWithProbability1 = storm::utility::graph::performProb1E(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); - + storm::storage::MaximalEndComponentDecomposition mecDecomposition(relevantStatesMatrix, relevantStatesMatrix.transpose(true)); ++stats.ecDetections; STORM_LOG_TRACE("Successfully computed MEC decomposition. Found " << (mecDecomposition.size() > 1 ? (mecDecomposition.size() - 1) : 0) << " MEC(s)."); - + // If the decomposition contains only the MEC consisting of the sink state, we count it as 'failed'. if (mecDecomposition.size() > 1) { ++stats.failedEcDetections; @@ -473,13 +472,9 @@ namespace storm { statesWithProbability1 = storm::utility::graph::performProb1A(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates); } - std::cout << statesWithProbability0 << std::endl; - std::cout << statesWithProbability1 << std::endl; - // Set the bounds of the identified states. for (auto state : statesWithProbability0) { StateType originalState = relevantStates[state]; - std::cout << "determined " << originalState << " as a prob0 state" << std::endl; bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero()); explorationInformation.addTerminalState(originalState); } @@ -588,7 +583,7 @@ namespace storm { } return result; } - + template std::pair SparseMdpExplorationModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { StateType group = explorationInformation.getRowGroup(currentStateId); @@ -620,7 +615,7 @@ namespace storm { // Check if we need to update the values for the states. if (explorationInformation.maximize()) { bounds.setLowerBoundOfStateIfGreaterThanOld(state, explorationInformation, newBoundsForAction.first); - + StateType rowGroup = explorationInformation.getRowGroup(state); if (newBoundsForAction.second < bounds.getUpperBoundForRowGroup(rowGroup)) { if (explorationInformation.getRowGroupSize(rowGroup) > 1) { @@ -631,7 +626,7 @@ namespace storm { } } else { bounds.setUpperBoundOfStateIfLessThanOld(state, explorationInformation, newBoundsForAction.second); - + StateType rowGroup = explorationInformation.getRowGroup(state); if (bounds.getLowerBoundForRowGroup(rowGroup) < newBoundsForAction.first) { if (explorationInformation.getRowGroupSize(rowGroup) > 1) { diff --git a/src/settings/modules/ExplorationSettings.cpp b/src/settings/modules/ExplorationSettings.cpp index 4a3a2796d..306d63aa8 100644 --- a/src/settings/modules/ExplorationSettings.cpp +++ b/src/settings/modules/ExplorationSettings.cpp @@ -22,8 +22,8 @@ namespace storm { this->addOption(storm::settings::OptionBuilder(moduleName, numberOfExplorationStepsUntilPrecomputationOptionName, true, "Sets the number of exploration steps to perform until a precomputation is triggered.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of exploration steps to perform.").setDefaultValueUnsignedInteger(100000).build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, numberOfSampledPathsUntilPrecomputationOptionName, true, "If set, a precomputation is perfomed periodically after the given number of paths has been sampled.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("count", "The number of paths to sample until a precomputation is triggered.").setDefaultValueUnsignedInteger(100000).build()).build()); - std::vector nextStateHeuristics = { "probdiff", "prob" }; - this->addOption(storm::settings::OptionBuilder(moduleName, nextStateHeuristicOptionName, true, "Sets the next-state heuristic to use. Available are: { probdiff, prob } where 'prob' samples according to the probabilities in the system and 'probdiff' weights the probabilities with the differences between the current bounds.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the heuristic to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(nextStateHeuristics)).setDefaultValueString("probdiff").build()).build()); + std::vector nextStateHeuristics = { "probdiffs", "prob", "unif" }; + this->addOption(storm::settings::OptionBuilder(moduleName, nextStateHeuristicOptionName, true, "Sets the next-state heuristic to use. Available are: { probdiffs, prob, unif } where 'prob' samples according to the probabilities in the system, 'probdiffs' takes into account probabilities and the differences between the current bounds and 'unif' samples uniformly.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the heuristic to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(nextStateHeuristics)).setDefaultValueString("probdiffs").build()).build()); } bool ExplorationSettings::isLocalPrecomputationSet() const { @@ -64,10 +64,12 @@ namespace storm { ExplorationSettings::NextStateHeuristic ExplorationSettings::getNextStateHeuristic() const { std::string nextStateHeuristicAsString = this->getOption(nextStateHeuristicOptionName).getArgumentByName("name").getValueAsString(); - if (nextStateHeuristicAsString == "probdiff") { - return ExplorationSettings::NextStateHeuristic::DifferenceWeightedProbability; + if (nextStateHeuristicAsString == "probdiffs") { + return ExplorationSettings::NextStateHeuristic::DifferenceProbabilitySum; } else if (nextStateHeuristicAsString == "prob") { return ExplorationSettings::NextStateHeuristic::Probability; + } else if (nextStateHeuristicAsString == "unif") { + return ExplorationSettings::NextStateHeuristic::Uniform; } STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown next-state heuristic '" << nextStateHeuristicAsString << "'."); } diff --git a/src/settings/modules/ExplorationSettings.h b/src/settings/modules/ExplorationSettings.h index 10dcaf080..18126acfe 100644 --- a/src/settings/modules/ExplorationSettings.h +++ b/src/settings/modules/ExplorationSettings.h @@ -16,7 +16,7 @@ namespace storm { enum class PrecomputationType { Local, Global }; // The available heuristics to choose the next state. - enum class NextStateHeuristic { DifferenceWeightedProbability, Probability }; + enum class NextStateHeuristic { DifferenceProbabilitySum, Probability, Uniform }; /*! * Creates a new set of exploration settings that is managed by the given manager. From 60bbce0ba1fea5c095262b5b36b0fb100ce3f394 Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 15 Apr 2016 13:56:06 +0200 Subject: [PATCH 30/31] added two tests for exploration engine Former-commit-id: 960393b2296e6050feab26b1193fdcc22dbc0d1c --- src/cli/entrypoints.h | 2 +- src/logic/FragmentSpecification.cpp | 1 + src/modelchecker/CheckTask.h | 2 +- ....cpp => SparseExplorationModelChecker.cpp} | 92 +++++++++---------- ...cker.h => SparseExplorationModelChecker.h} | 13 ++- src/settings/modules/ExplorationSettings.cpp | 9 ++ src/settings/modules/ExplorationSettings.h | 9 ++ src/utility/storm.h | 2 +- .../GmmxxDtmcPrctlModelCheckerTest.cpp | 8 +- .../NativeDtmcPrctlModelCheckerTest.cpp | 16 ++-- .../NativeMdpPrctlModelCheckerTest.cpp | 36 ++++---- .../SparseExplorationModelCheckerTest.cpp | 86 +++++++++++++++++ 12 files changed, 190 insertions(+), 86 deletions(-) rename src/modelchecker/exploration/{SparseMdpExplorationModelChecker.cpp => SparseExplorationModelChecker.cpp} (85%) rename src/modelchecker/exploration/{SparseMdpExplorationModelChecker.h => SparseExplorationModelChecker.h} (90%) create mode 100644 test/functional/modelchecker/SparseExplorationModelCheckerTest.cpp diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index 17291f750..8748c5a17 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -61,7 +61,7 @@ namespace storm { STORM_LOG_THROW(program.getModelType() == storm::prism::Program::ModelType::DTMC || program.getModelType() == storm::prism::Program::ModelType::MDP, storm::exceptions::InvalidSettingsException, "Currently exploration-based verification is only available for DTMCs and MDPs."); std::cout << std::endl << "Model checking property: " << *formula << " ..."; storm::modelchecker::CheckTask task(*formula, onlyInitialStatesRelevant); - storm::modelchecker::SparseMdpExplorationModelChecker checker(program, storm::utility::prism::parseConstantDefinitionString(program, storm::settings::generalSettings().getConstantDefinitionString())); + storm::modelchecker::SparseExplorationModelChecker checker(program, storm::utility::prism::parseConstantDefinitionString(program, storm::settings::generalSettings().getConstantDefinitionString())); std::unique_ptr result; if (checker.canHandle(task)) { result = checker.check(task); diff --git a/src/logic/FragmentSpecification.cpp b/src/logic/FragmentSpecification.cpp index 6877496d2..3dd2bf12b 100644 --- a/src/logic/FragmentSpecification.cpp +++ b/src/logic/FragmentSpecification.cpp @@ -414,6 +414,7 @@ namespace storm { FragmentSpecification& FragmentSpecification::setOperatorAtTopLevelRequired(bool newValue) { operatorAtTopLevelRequired = newValue; + return *this; } } diff --git a/src/modelchecker/CheckTask.h b/src/modelchecker/CheckTask.h index 8693f09f2..3d45dd5d4 100644 --- a/src/modelchecker/CheckTask.h +++ b/src/modelchecker/CheckTask.h @@ -23,7 +23,7 @@ namespace storm { /* * This class is used to customize the checking process of a formula. */ - template + template class CheckTask { public: template diff --git a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp b/src/modelchecker/exploration/SparseExplorationModelChecker.cpp similarity index 85% rename from src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp rename to src/modelchecker/exploration/SparseExplorationModelChecker.cpp index 542273713..322af7999 100644 --- a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.cpp +++ b/src/modelchecker/exploration/SparseExplorationModelChecker.cpp @@ -1,4 +1,4 @@ -#include "src/modelchecker/exploration/SparseMdpExplorationModelChecker.h" +#include "src/modelchecker/exploration/SparseExplorationModelChecker.h" #include "src/modelchecker/exploration/ExplorationInformation.h" #include "src/modelchecker/exploration/StateGeneration.h" @@ -33,20 +33,20 @@ namespace storm { namespace modelchecker { - template - SparseMdpExplorationModelChecker::SparseMdpExplorationModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions) : program(storm::utility::prism::preprocessProgram(program, constantDefinitions)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator() { + template + SparseExplorationModelChecker::SparseExplorationModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions) : program(storm::utility::prism::preprocessProgram(program, constantDefinitions)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator(storm::settings::explorationSettings().getPrecision()) { // Intentionally left empty. } - template - bool SparseMdpExplorationModelChecker::canHandle(CheckTask const& checkTask) const { + template + bool SparseExplorationModelChecker::canHandle(CheckTask const& checkTask) const { storm::logic::Formula const& formula = checkTask.getFormula(); storm::logic::FragmentSpecification fragment = storm::logic::reachability(); return formula.isInFragment(fragment) && checkTask.isOnlyInitialStatesRelevantSet(); } - template - std::unique_ptr SparseMdpExplorationModelChecker::computeUntilProbabilities(CheckTask const& checkTask) { + template + std::unique_ptr SparseExplorationModelChecker::computeUntilProbabilities(CheckTask const& checkTask) { storm::logic::UntilFormula const& untilFormula = checkTask.getFormula(); storm::logic::Formula const& conditionFormula = untilFormula.getLeftSubformula(); storm::logic::Formula const& targetFormula = untilFormula.getRightSubformula(); @@ -66,8 +66,8 @@ namespace storm { return std::make_unique>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); } - template - std::tuple::StateType, ValueType, ValueType> SparseMdpExplorationModelChecker::performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { + template + std::tuple SparseExplorationModelChecker::performExploration(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation) const { // Generate the initial state so we know where to start the simulation. stateGeneration.computeInitialStates(); STORM_LOG_THROW(stateGeneration.getNumberOfInitialStates() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the exploration engine."); @@ -119,8 +119,8 @@ namespace storm { return std::make_tuple(initialStateIndex, bounds.getLowerBoundForState(initialStateIndex, explorationInformation), bounds.getUpperBoundForState(initialStateIndex, explorationInformation)); } - template - bool SparseMdpExplorationModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, Bounds& bounds, Statistics& stats) const { + template + bool SparseExplorationModelChecker::samplePathFromInitialState(StateGeneration& stateGeneration, ExplorationInformation& explorationInformation, StateActionStack& stack, Bounds& bounds, Statistics& stats) const { // Start the search from the initial state. stack.push_back(std::make_pair(stateGeneration.getFirstInitialState(), 0)); @@ -181,8 +181,8 @@ namespace storm { return foundTerminalState; } - template - bool SparseMdpExplorationModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const { + template + bool SparseExplorationModelChecker::exploreState(StateGeneration& stateGeneration, StateType const& currentStateId, storm::generator::CompressedState const& currentState, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const { bool isTerminalState = false; bool isTargetState = false; @@ -283,8 +283,8 @@ namespace storm { return isTerminalState; } - template - typename SparseMdpExplorationModelChecker::ActionType SparseMdpExplorationModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds& bounds) const { + template + typename SparseExplorationModelChecker::ActionType SparseExplorationModelChecker::sampleActionOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds& bounds) const { // Determine the values of all available actions. std::vector> actionValues; StateType rowGroup = explorationInformation.getRowGroup(currentStateId); @@ -321,14 +321,13 @@ namespace storm { return actionValues[distribution(randomGenerator)].first; } - template - typename SparseMdpExplorationModelChecker::StateType SparseMdpExplorationModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { + template + StateType SparseExplorationModelChecker::sampleSuccessorFromAction(ActionType const& chosenAction, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { std::vector> const& row = explorationInformation.getRowOfMatrix(chosenAction); if (row.size() == 1) { return row.front().getColumn(); } - // Depending on the selected next-state heuristic, we give the states other likelihoods of getting chosen. if (explorationInformation.useDifferenceProbabilitySumHeuristic() || explorationInformation.useProbabilityHeuristic()) { std::vector probabilities(row.size()); @@ -347,14 +346,15 @@ namespace storm { // Now sample according to the probabilities. std::discrete_distribution distribution(probabilities.begin(), probabilities.end()); return row[distribution(randomGenerator)].getColumn(); - } else if (explorationInformation.useUniformHeuristic()) { + } else { + STORM_LOG_ASSERT(explorationInformation.useUniformHeuristic(), "Illegal next-state heuristic."); std::uniform_int_distribution distribution(0, row.size() - 1); return row[distribution(randomGenerator)].getColumn(); } } - template - bool SparseMdpExplorationModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const { + template + bool SparseExplorationModelChecker::performPrecomputation(StateActionStack const& stack, ExplorationInformation& explorationInformation, Bounds& bounds, Statistics& stats) const { ++stats.numberOfPrecomputations; // Outline: @@ -370,7 +370,7 @@ namespace storm { std::vector relevantStates; if (explorationInformation.useLocalPrecomputation()) { for (auto const& stateActionPair : stack) { - if (explorationInformation.maximize() || !comparator.isOne(bounds.getLowerBoundForState(stateActionPair.first, explorationInformation))) { + if (explorationInformation.maximize() || !storm::utility::isOne(bounds.getLowerBoundForState(stateActionPair.first, explorationInformation))) { relevantStates.push_back(stateActionPair.first); } } @@ -486,8 +486,8 @@ namespace storm { return true; } - template - void SparseMdpExplorationModelChecker::collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, Bounds& bounds) const { + template + void SparseExplorationModelChecker::collapseMec(storm::storage::MaximalEndComponent const& mec, std::vector const& relevantStates, storm::storage::SparseMatrix const& relevantStatesMatrix, ExplorationInformation& explorationInformation, Bounds& bounds) const { bool containsTargetState = false; // Now we record all actions leaving the EC. @@ -498,7 +498,7 @@ namespace storm { StateType originalRowGroup = explorationInformation.getRowGroup(originalState); // Check whether a target state is contained in the MEC. - if (!containsTargetState && comparator.isOne(bounds.getLowerBoundForRowGroup(originalRowGroup))) { + if (!containsTargetState && storm::utility::isOne(bounds.getLowerBoundForRowGroup(originalRowGroup))) { containsTargetState = true; } @@ -555,8 +555,8 @@ namespace storm { } } - template - ValueType SparseMdpExplorationModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { + template + ValueType SparseExplorationModelChecker::computeLowerBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { ValueType result = storm::utility::zero(); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { result += element.getValue() * bounds.getLowerBoundForState(element.getColumn(), explorationInformation); @@ -564,8 +564,8 @@ namespace storm { return result; } - template - ValueType SparseMdpExplorationModelChecker::computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { + template + ValueType SparseExplorationModelChecker::computeUpperBoundOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { ValueType result = storm::utility::zero(); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { result += element.getValue() * bounds.getUpperBoundForState(element.getColumn(), explorationInformation); @@ -573,8 +573,8 @@ namespace storm { return result; } - template - std::pair SparseMdpExplorationModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { + template + std::pair SparseExplorationModelChecker::computeBoundsOfAction(ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { // TODO: take into account self-loops? std::pair result = std::make_pair(storm::utility::zero(), storm::utility::zero()); for (auto const& element : explorationInformation.getRowOfMatrix(action)) { @@ -584,8 +584,8 @@ namespace storm { return result; } - template - std::pair SparseMdpExplorationModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { + template + std::pair SparseExplorationModelChecker::computeBoundsOfState(StateType const& currentStateId, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { StateType group = explorationInformation.getRowGroup(currentStateId); std::pair result = getLowestBounds(explorationInformation.getOptimizationDirection()); for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) { @@ -595,8 +595,8 @@ namespace storm { return result; } - template - void SparseMdpExplorationModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, Bounds& bounds) const { + template + void SparseExplorationModelChecker::updateProbabilityBoundsAlongSampledPath(StateActionStack& stack, ExplorationInformation const& explorationInformation, Bounds& bounds) const { stack.pop_back(); while (!stack.empty()) { updateProbabilityOfAction(stack.back().first, stack.back().second, explorationInformation, bounds); @@ -604,8 +604,8 @@ namespace storm { } } - template - void SparseMdpExplorationModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds& bounds) const { + template + void SparseExplorationModelChecker::updateProbabilityOfAction(StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds& bounds) const { // Compute the new lower/upper values of the action. std::pair newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds); @@ -639,8 +639,8 @@ namespace storm { } } - template - ValueType SparseMdpExplorationModelChecker::computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { + template + ValueType SparseExplorationModelChecker::computeBoundOverAllOtherActions(storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action, ExplorationInformation const& explorationInformation, Bounds const& bounds) const { ValueType bound = getLowestBound(explorationInformation.getOptimizationDirection()); ActionType group = explorationInformation.getRowGroup(state); @@ -658,14 +658,14 @@ namespace storm { return bound; } - template - std::pair SparseMdpExplorationModelChecker::getLowestBounds(storm::OptimizationDirection const& direction) const { + template + std::pair SparseExplorationModelChecker::getLowestBounds(storm::OptimizationDirection const& direction) const { ValueType val = getLowestBound(direction); return std::make_pair(val, val); } - template - ValueType SparseMdpExplorationModelChecker::getLowestBound(storm::OptimizationDirection const& direction) const { + template + ValueType SparseExplorationModelChecker::getLowestBound(storm::OptimizationDirection const& direction) const { if (direction == storm::OptimizationDirection::Maximize) { return storm::utility::zero(); } else { @@ -673,8 +673,8 @@ namespace storm { } } - template - std::pair SparseMdpExplorationModelChecker::combineBounds(storm::OptimizationDirection const& direction, std::pair const& bounds1, std::pair const& bounds2) const { + template + std::pair SparseExplorationModelChecker::combineBounds(storm::OptimizationDirection const& direction, std::pair const& bounds1, std::pair const& bounds2) const { if (direction == storm::OptimizationDirection::Maximize) { return std::make_pair(std::max(bounds1.first, bounds2.first), std::max(bounds1.second, bounds2.second)); } else { @@ -682,6 +682,6 @@ namespace storm { } } - template class SparseMdpExplorationModelChecker; + template class SparseExplorationModelChecker; } } \ No newline at end of file diff --git a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h b/src/modelchecker/exploration/SparseExplorationModelChecker.h similarity index 90% rename from src/modelchecker/exploration/SparseMdpExplorationModelChecker.h rename to src/modelchecker/exploration/SparseExplorationModelChecker.h index 5db171722..aa13f379a 100644 --- a/src/modelchecker/exploration/SparseMdpExplorationModelChecker.h +++ b/src/modelchecker/exploration/SparseExplorationModelChecker.h @@ -1,5 +1,5 @@ -#ifndef STORM_MODELCHECKER_EXPLORATION_SPARSEMDPEXPLORATIONMODELCHECKER_H_ -#define STORM_MODELCHECKER_EXPLORATION_SPARSEMDPEXPLORATIONMODELCHECKER_H_ +#ifndef STORM_MODELCHECKER_EXPLORATION_SPARSEEXPLORATIONMODELCHECKER_H_ +#define STORM_MODELCHECKER_EXPLORATION_SPARSEEXPLORATIONMODELCHECKER_H_ #include @@ -30,14 +30,13 @@ namespace storm { using namespace exploration_detail; - template - class SparseMdpExplorationModelChecker : public AbstractModelChecker { + template + class SparseExplorationModelChecker : public AbstractModelChecker { public: - typedef uint32_t StateType; typedef StateType ActionType; typedef std::vector> StateActionStack; - SparseMdpExplorationModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions); + SparseExplorationModelChecker(storm::prism::Program const& program, boost::optional> const& constantDefinitions = boost::none); virtual bool canHandle(CheckTask const& checkTask) const override; @@ -87,4 +86,4 @@ namespace storm { } } -#endif /* STORM_MODELCHECKER_EXPLORATION_SPARSEMDPEXPLORATIONMODELCHECKER_H_ */ \ No newline at end of file +#endif /* STORM_MODELCHECKER_EXPLORATION_SPARSEEXPLORATIONMODELCHECKER_H_ */ \ No newline at end of file diff --git a/src/settings/modules/ExplorationSettings.cpp b/src/settings/modules/ExplorationSettings.cpp index 306d63aa8..688f4e6b6 100644 --- a/src/settings/modules/ExplorationSettings.cpp +++ b/src/settings/modules/ExplorationSettings.cpp @@ -15,6 +15,8 @@ namespace storm { const std::string ExplorationSettings::numberOfExplorationStepsUntilPrecomputationOptionName = "stepsprecomp"; const std::string ExplorationSettings::numberOfSampledPathsUntilPrecomputationOptionName = "pathsprecomp"; const std::string ExplorationSettings::nextStateHeuristicOptionName = "nextstate"; + const std::string ExplorationSettings::precisionOptionName = "precision"; + const std::string ExplorationSettings::precisionOptionShortName = "eps"; ExplorationSettings::ExplorationSettings(storm::settings::SettingsManager& settingsManager) : ModuleSettings(settingsManager, moduleName) { std::vector types = { "local", "global" }; @@ -24,6 +26,9 @@ namespace storm { std::vector nextStateHeuristics = { "probdiffs", "prob", "unif" }; this->addOption(storm::settings::OptionBuilder(moduleName, nextStateHeuristicOptionName, true, "Sets the next-state heuristic to use. Available are: { probdiffs, prob, unif } where 'prob' samples according to the probabilities in the system, 'probdiffs' takes into account probabilities and the differences between the current bounds and 'unif' samples uniformly.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the heuristic to use.").addValidationFunctionString(storm::settings::ArgumentValidators::stringInListValidator(nextStateHeuristics)).setDefaultValueString("probdiffs").build()).build()); + + this->addOption(storm::settings::OptionBuilder(moduleName, precisionOptionName, false, "The internally used precision.").setShortName(precisionOptionShortName) + .addArgument(storm::settings::ArgumentBuilder::createDoubleArgument("value", "The precision to use.").setDefaultValueDouble(1e-06).addValidationFunctionDouble(storm::settings::ArgumentValidators::doubleRangeValidatorExcluding(0.0, 1.0)).build()).build()); } bool ExplorationSettings::isLocalPrecomputationSet() const { @@ -74,6 +79,10 @@ namespace storm { STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentValueException, "Unknown next-state heuristic '" << nextStateHeuristicAsString << "'."); } + double ExplorationSettings::getPrecision() const { + return this->getOption(precisionOptionName).getArgumentByName("value").getValueAsDouble(); + } + bool ExplorationSettings::check() const { bool optionsSet = this->getOption(precomputationTypeOptionName).getHasOptionBeenSet() || this->getOption(numberOfExplorationStepsUntilPrecomputationOptionName).getHasOptionBeenSet() || diff --git a/src/settings/modules/ExplorationSettings.h b/src/settings/modules/ExplorationSettings.h index 18126acfe..8f72785eb 100644 --- a/src/settings/modules/ExplorationSettings.h +++ b/src/settings/modules/ExplorationSettings.h @@ -74,6 +74,13 @@ namespace storm { */ NextStateHeuristic getNextStateHeuristic() const; + /*! + * Retrieves the precision to use for numerical operations. + * + * @return The precision to use for numerical operations. + */ + double getPrecision() const; + virtual bool check() const override; // The name of the module. @@ -85,6 +92,8 @@ namespace storm { static const std::string numberOfExplorationStepsUntilPrecomputationOptionName; static const std::string numberOfSampledPathsUntilPrecomputationOptionName; static const std::string nextStateHeuristicOptionName; + static const std::string precisionOptionName; + static const std::string precisionOptionShortName; }; } // namespace modules } // namespace settings diff --git a/src/utility/storm.h b/src/utility/storm.h index d5713a142..48020630a 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -57,7 +57,7 @@ #include "src/modelchecker/prctl/SymbolicDtmcPrctlModelChecker.h" #include "src/modelchecker/prctl/SymbolicMdpPrctlModelChecker.h" #include "src/modelchecker/reachability/SparseDtmcEliminationModelChecker.h" -#include "src/modelchecker/exploration/SparseMdpExplorationModelChecker.h" +#include "src/modelchecker/exploration/SparseExplorationModelChecker.h" #include "src/modelchecker/csl/SparseCtmcCslModelChecker.h" #include "src/modelchecker/csl/HybridCtmcCslModelChecker.h" #include "src/modelchecker/csl/SparseMarkovAutomatonCslModelChecker.h" diff --git a/test/functional/modelchecker/GmmxxDtmcPrctlModelCheckerTest.cpp b/test/functional/modelchecker/GmmxxDtmcPrctlModelCheckerTest.cpp index 36a139b9c..715489715 100644 --- a/test/functional/modelchecker/GmmxxDtmcPrctlModelCheckerTest.cpp +++ b/test/functional/modelchecker/GmmxxDtmcPrctlModelCheckerTest.cpp @@ -157,7 +157,7 @@ TEST(GmmxxDtmcPrctlModelCheckerTest, LRASingleBscc) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -181,7 +181,7 @@ TEST(GmmxxDtmcPrctlModelCheckerTest, LRASingleBscc) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -205,7 +205,7 @@ TEST(GmmxxDtmcPrctlModelCheckerTest, LRASingleBscc) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1. / 3., quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -268,7 +268,7 @@ TEST(GmmxxDtmcPrctlModelCheckerTest, LRA) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.3 / 3., quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); diff --git a/test/functional/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp b/test/functional/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp index 6af4c51cc..253417bad 100644 --- a/test/functional/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp +++ b/test/functional/modelchecker/NativeDtmcPrctlModelCheckerTest.cpp @@ -157,8 +157,8 @@ TEST(SparseDtmcPrctlModelCheckerTest, LRASingleBscc) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); - storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); + std::unique_ptr result = checker.check(*formula); + storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); EXPECT_NEAR(.5, quantitativeResult1[1], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -181,8 +181,8 @@ TEST(SparseDtmcPrctlModelCheckerTest, LRASingleBscc) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); - storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); + std::unique_ptr result = checker.check(*formula); + storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); EXPECT_NEAR(.5, quantitativeResult1[1], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -205,8 +205,8 @@ TEST(SparseDtmcPrctlModelCheckerTest, LRASingleBscc) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); - storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); + std::unique_ptr result = checker.check(*formula); + storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1. / 3., quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); EXPECT_NEAR(1. / 3., quantitativeResult1[1], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -268,8 +268,8 @@ TEST(SparseDtmcPrctlModelCheckerTest, LRA) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRA=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); - storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); + std::unique_ptr result = checker.check(*formula); + storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.3 / 3., quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); EXPECT_NEAR(0.0, quantitativeResult1[3], storm::settings::nativeEquationSolverSettings().getPrecision()); diff --git a/test/functional/modelchecker/NativeMdpPrctlModelCheckerTest.cpp b/test/functional/modelchecker/NativeMdpPrctlModelCheckerTest.cpp index da5a2cd34..85c38e505 100644 --- a/test/functional/modelchecker/NativeMdpPrctlModelCheckerTest.cpp +++ b/test/functional/modelchecker/NativeMdpPrctlModelCheckerTest.cpp @@ -211,7 +211,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -219,7 +219,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"a\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult2[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -243,7 +243,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -251,7 +251,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"a\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(.5, quantitativeResult2[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -284,7 +284,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1. / 3., quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -293,7 +293,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"a\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.0, quantitativeResult2[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -302,7 +302,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"b\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult3 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.5, quantitativeResult3[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -311,7 +311,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"b\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult4 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1. / 3., quantitativeResult4[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -320,7 +320,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"c\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult5 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(2. / 3., quantitativeResult5[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -329,7 +329,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA_SingleMec) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"c\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult6 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.5, quantitativeResult6[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -370,7 +370,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.0, quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -379,7 +379,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"a\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.0, quantitativeResult2[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -388,7 +388,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"b\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult3 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1.0, quantitativeResult3[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -397,7 +397,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"b\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult4 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.0, quantitativeResult4[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -406,7 +406,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"c\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult5 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(1.0, quantitativeResult5[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -415,7 +415,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"c\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult6 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.0, quantitativeResult6[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -492,7 +492,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("LRAmax=? [\"a\"]"); - std::unique_ptr result = std::move(checker.check(*formula)); + std::unique_ptr result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(37. / 60., quantitativeResult1[0], storm::settings::nativeEquationSolverSettings().getPrecision()); @@ -505,7 +505,7 @@ TEST(SparseMdpPrctlModelCheckerTest, LRA) { formula = formulaParser.parseSingleFormulaFromString("LRAmin=? [\"a\"]"); - result = std::move(checker.check(*formula)); + result = checker.check(*formula); storm::modelchecker::ExplicitQuantitativeCheckResult& quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); EXPECT_NEAR(0.3 / 3., quantitativeResult2[0], storm::settings::nativeEquationSolverSettings().getPrecision()); diff --git a/test/functional/modelchecker/SparseExplorationModelCheckerTest.cpp b/test/functional/modelchecker/SparseExplorationModelCheckerTest.cpp new file mode 100644 index 000000000..0e7304f81 --- /dev/null +++ b/test/functional/modelchecker/SparseExplorationModelCheckerTest.cpp @@ -0,0 +1,86 @@ +#include "gtest/gtest.h" +#include "storm-config.h" + +#include "src/logic/Formulas.h" +#include "src/modelchecker/exploration/SparseExplorationModelChecker.h" +#include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "src/parser/PrismParser.h" +#include "src/parser/FormulaParser.h" + +#include "src/settings/SettingsManager.h" +#include "src/settings/modules/ExplorationSettings.h" + +TEST(SparseExplorationModelCheckerTest, Dice) { + storm::prism::Program program = storm::parser::PrismParser::parse(STORM_CPP_TESTS_BASE_PATH "/functional/builder/two_dice.nm"); + + // A parser that we use for conveniently constructing the formulas. + storm::parser::FormulaParser formulaParser; + + storm::modelchecker::SparseExplorationModelChecker checker(program); + + std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("Pmin=? [F \"two\"]"); + + std::unique_ptr result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(0.0277777612209320068, quantitativeResult1[0], storm::settings::explorationSettings().getPrecision()); + + formula = formulaParser.parseSingleFormulaFromString("Pmax=? [F \"two\"]"); + + result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(0.0277777612209320068, quantitativeResult2[0], storm::settings::explorationSettings().getPrecision()); + + formula = formulaParser.parseSingleFormulaFromString("Pmin=? [F \"three\"]"); + + result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult3 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(0.0555555224418640136, quantitativeResult3[0], storm::settings::explorationSettings().getPrecision()); + + formula = formulaParser.parseSingleFormulaFromString("Pmax=? [F \"three\"]"); + + result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult4 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(0.0555555224418640136, quantitativeResult4[0], storm::settings::explorationSettings().getPrecision()); + + formula = formulaParser.parseSingleFormulaFromString("Pmin=? [F \"four\"]"); + + result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult5 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(0.083333283662796020508, quantitativeResult5[0], storm::settings::explorationSettings().getPrecision()); + + formula = formulaParser.parseSingleFormulaFromString("Pmax=? [F \"four\"]"); + + result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult6 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(0.083333283662796020508, quantitativeResult6[0], storm::settings::explorationSettings().getPrecision()); +} + + +TEST(SparseExplorationModelCheckerTest, AsynchronousLeader) { + storm::prism::Program program = storm::parser::PrismParser::parse(STORM_CPP_TESTS_BASE_PATH "/functional/builder/leader4.nm"); + + // A parser that we use for conveniently constructing the formulas. + storm::parser::FormulaParser formulaParser; + + storm::modelchecker::SparseExplorationModelChecker checker(program); + + std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("Pmin=? [F \"elected\"]"); + + std::unique_ptr result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult1 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(1, quantitativeResult1[0], storm::settings::explorationSettings().getPrecision()); + + formula = formulaParser.parseSingleFormulaFromString("Pmax=? [F \"elected\"]"); + + result = checker.check(storm::modelchecker::CheckTask<>(*formula, true)); + storm::modelchecker::ExplicitQuantitativeCheckResult const& quantitativeResult2 = result->asExplicitQuantitativeCheckResult(); + + EXPECT_NEAR(1, quantitativeResult2[0], storm::settings::explorationSettings().getPrecision()); +} From 37220cae577c2ff23633672a58d8e9fa072259bc Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 15 Apr 2016 14:10:52 +0200 Subject: [PATCH 31/31] removed two assertions in tests because they no longer apply Former-commit-id: fcf132e68594da37fd2e3a0c1b83fd1cf83ec877 --- .../parser/DeterministicSparseTransitionParserTest.cpp | 2 -- .../parser/NondeterministicSparseTransitionParserTest.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/test/functional/parser/DeterministicSparseTransitionParserTest.cpp b/test/functional/parser/DeterministicSparseTransitionParserTest.cpp index 50b545d25..dfffcec9c 100644 --- a/test/functional/parser/DeterministicSparseTransitionParserTest.cpp +++ b/test/functional/parser/DeterministicSparseTransitionParserTest.cpp @@ -183,11 +183,9 @@ TEST(DeterministicSparseTransitionParserTest, Whitespaces) { TEST(DeterministicSparseTransitionParserTest, MixedTransitionOrder) { // Since the MatrixBuilder needs sequential input of new elements reordering of transitions or states should throw an exception. - ASSERT_THROW(storm::parser::DeterministicSparseTransitionParser<>::parseDeterministicTransitions(STORM_CPP_TESTS_BASE_PATH "/functional/parser/tra_files/dtmc_mixedTransitionOrder.tra"), storm::exceptions::InvalidArgumentException); ASSERT_THROW(storm::parser::DeterministicSparseTransitionParser<>::parseDeterministicTransitions(STORM_CPP_TESTS_BASE_PATH "/functional/parser/tra_files/dtmc_mixedStateOrder.tra"), storm::exceptions::InvalidArgumentException); storm::storage::SparseMatrix transitionMatrix = storm::parser::DeterministicSparseTransitionParser<>::parseDeterministicTransitions(STORM_CPP_TESTS_BASE_PATH "/functional/parser/tra_files/dtmc_general.tra"); - ASSERT_THROW(storm::parser::DeterministicSparseTransitionParser<>::parseDeterministicTransitionRewards(STORM_CPP_TESTS_BASE_PATH "/functional/parser/rew_files/dtmc_mixedTransitionOrder.trans.rew", transitionMatrix), storm::exceptions::InvalidArgumentException); ASSERT_THROW(storm::parser::DeterministicSparseTransitionParser<>::parseDeterministicTransitionRewards(STORM_CPP_TESTS_BASE_PATH "/functional/parser/rew_files/dtmc_mixedStateOrder.trans.rew", transitionMatrix), storm::exceptions::InvalidArgumentException); } diff --git a/test/functional/parser/NondeterministicSparseTransitionParserTest.cpp b/test/functional/parser/NondeterministicSparseTransitionParserTest.cpp index c5c0807bc..57af1b7d8 100644 --- a/test/functional/parser/NondeterministicSparseTransitionParserTest.cpp +++ b/test/functional/parser/NondeterministicSparseTransitionParserTest.cpp @@ -202,11 +202,9 @@ TEST(NondeterministicSparseTransitionParserTest, Whitespaces) { TEST(NondeterministicSparseTransitionParserTest, MixedTransitionOrder) { // Since the MatrixBuilder needs sequential input of new elements reordering of transitions or states should throw an exception. - ASSERT_THROW(storm::parser::NondeterministicSparseTransitionParser<>::parseNondeterministicTransitions(STORM_CPP_TESTS_BASE_PATH "/functional/parser/tra_files/mdp_mixedTransitionOrder.tra"), storm::exceptions::InvalidArgumentException); ASSERT_THROW(storm::parser::NondeterministicSparseTransitionParser<>::parseNondeterministicTransitions(STORM_CPP_TESTS_BASE_PATH "/functional/parser/tra_files/mdp_mixedStateOrder.tra"), storm::exceptions::InvalidArgumentException); storm::storage::SparseMatrix modelInformation = storm::parser::NondeterministicSparseTransitionParser<>::parseNondeterministicTransitions(STORM_CPP_TESTS_BASE_PATH "/functional/parser/tra_files/mdp_general.tra"); - ASSERT_THROW(storm::parser::NondeterministicSparseTransitionParser<>::parseNondeterministicTransitionRewards(STORM_CPP_TESTS_BASE_PATH "/functional/parser/rew_files/mdp_mixedTransitionOrder.trans.rew", modelInformation), storm::exceptions::InvalidArgumentException); ASSERT_THROW(storm::parser::NondeterministicSparseTransitionParser<>::parseNondeterministicTransitionRewards(STORM_CPP_TESTS_BASE_PATH "/functional/parser/rew_files/mdp_mixedStateOrder.trans.rew", modelInformation), storm::exceptions::InvalidArgumentException); }