diff --git a/src/storm-cli-utilities/model-handling.h b/src/storm-cli-utilities/model-handling.h index 4180bdc33..81264a267 100644 --- a/src/storm-cli-utilities/model-handling.h +++ b/src/storm-cli-utilities/model-handling.h @@ -377,8 +377,6 @@ namespace storm { auto counterexampleSettings = storm::settings::getModule(); if (counterexampleSettings.isMinimalCommandSetGenerationSet()) { - STORM_LOG_THROW(input.model && input.model.get().isPrismProgram(), storm::exceptions::NotSupportedException, "Minimal command set counterexamples are only supported for PRISM model input."); - storm::prism::Program const& program = input.model.get().asPrismProgram(); bool useMilp = counterexampleSettings.isUseMilpBasedMinimalCommandSetGenerationSet(); for (auto const& property : input.properties) { @@ -387,13 +385,17 @@ namespace storm { storm::utility::Stopwatch watch(true); if (useMilp) { STORM_LOG_THROW(sparseModel->isOfType(storm::models::ModelType::Mdp), storm::exceptions::NotSupportedException, "Counterexample generation using MILP is currently only supported for MDPs."); - counterexample = storm::api::computePrismHighLevelCounterexampleMilp(program, sparseModel->template as>(), property.getRawFormula()); + counterexample = storm::api::computeHighLevelCounterexampleMilp(input.model.get(), sparseModel->template as>(), property.getRawFormula()); } else { STORM_LOG_THROW(sparseModel->isOfType(storm::models::ModelType::Dtmc) || sparseModel->isOfType(storm::models::ModelType::Mdp), storm::exceptions::NotSupportedException, "Counterexample generation using MaxSAT is currently only supported for discrete-time models."); + + STORM_LOG_THROW(input.model && input.model.get().isPrismProgram(), storm::exceptions::NotSupportedException, "Minimal command set counterexamples are only supported for PRISM model input."); + storm::prism::Program const& program = input.model.get().asPrismProgram(); + if (sparseModel->isOfType(storm::models::ModelType::Dtmc)) { - counterexample = storm::api::computePrismHighLevelCounterexampleMaxSmt(program, sparseModel->template as>(), property.getRawFormula()); + counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(program, sparseModel->template as>(), property.getRawFormula()); } else { - counterexample = storm::api::computePrismHighLevelCounterexampleMaxSmt(program, sparseModel->template as>(), property.getRawFormula()); + counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(program, sparseModel->template as>(), property.getRawFormula()); } } watch.stop(); diff --git a/src/storm/api/counterexamples.cpp b/src/storm/api/counterexamples.cpp index 483dc57d4..3fce62b82 100644 --- a/src/storm/api/counterexamples.cpp +++ b/src/storm/api/counterexamples.cpp @@ -5,12 +5,12 @@ namespace storm { namespace api { - std::shared_ptr computePrismHighLevelCounterexampleMilp(storm::prism::Program const& program, std::shared_ptr> mdp, std::shared_ptr const& formula) { + std::shared_ptr computeHighLevelCounterexampleMilp(storm::storage::SymbolicModelDescription const& symbolicModel, std::shared_ptr> mdp, std::shared_ptr const& formula) { Environment env; - return storm::counterexamples::MILPMinimalLabelSetGenerator::computeCounterexample(env, program, *mdp, formula); + return storm::counterexamples::MILPMinimalLabelSetGenerator::computeCounterexample(env, symbolicModel, *mdp, formula); } - std::shared_ptr computePrismHighLevelCounterexampleMaxSmt(storm::prism::Program const& program, std::shared_ptr> model, std::shared_ptr const& formula) { + std::shared_ptr computeHighLevelCounterexampleMaxSmt(storm::prism::Program const& program, std::shared_ptr> model, std::shared_ptr const& formula) { Environment env; return storm::counterexamples::SMTMinimalLabelSetGenerator::computeCounterexample(env, program, *model, formula); } diff --git a/src/storm/api/counterexamples.h b/src/storm/api/counterexamples.h index 6481c4efd..90167b313 100644 --- a/src/storm/api/counterexamples.h +++ b/src/storm/api/counterexamples.h @@ -6,9 +6,9 @@ namespace storm { namespace api { - std::shared_ptr computePrismHighLevelCounterexampleMilp(storm::prism::Program const& program, std::shared_ptr> mdp, std::shared_ptr const& formula); + std::shared_ptr computeHighLevelCounterexampleMilp(storm::storage::SymbolicModelDescription const& symbolicModel, std::shared_ptr> mdp, std::shared_ptr const& formula); - std::shared_ptr computePrismHighLevelCounterexampleMaxSmt(storm::prism::Program const& program, std::shared_ptr> model, std::shared_ptr const& formula); + std::shared_ptr computeHighLevelCounterexampleMaxSmt(storm::prism::Program const& program, std::shared_ptr> model, std::shared_ptr const& formula); } } diff --git a/src/storm/builder/ChoiceInformationBuilder.cpp b/src/storm/builder/ChoiceInformationBuilder.cpp index 53d61c2f9..6578fe590 100644 --- a/src/storm/builder/ChoiceInformationBuilder.cpp +++ b/src/storm/builder/ChoiceInformationBuilder.cpp @@ -10,7 +10,7 @@ namespace storm { } void ChoiceInformationBuilder::addOriginData(boost::any const& originData, uint_fast64_t choiceIndex) { - if(dataOfOrigins.size() != choiceIndex) { + if (dataOfOrigins.size() != choiceIndex) { dataOfOrigins.resize(choiceIndex); } dataOfOrigins.push_back(originData); @@ -35,4 +35,4 @@ namespace storm { } } -} \ No newline at end of file +} diff --git a/src/storm/builder/ChoiceInformationBuilder.h b/src/storm/builder/ChoiceInformationBuilder.h index 1f9445330..8dbe8721a 100644 --- a/src/storm/builder/ChoiceInformationBuilder.h +++ b/src/storm/builder/ChoiceInformationBuilder.h @@ -31,11 +31,10 @@ namespace storm { std::vector buildDataOfChoiceOrigins(uint_fast64_t totalNumberOfChoices); - private: std::unordered_map labels; std::vector dataOfOrigins; }; } } - \ No newline at end of file + diff --git a/src/storm/counterexamples/HighLevelCounterexample.cpp b/src/storm/counterexamples/HighLevelCounterexample.cpp new file mode 100644 index 000000000..707ed6d39 --- /dev/null +++ b/src/storm/counterexamples/HighLevelCounterexample.cpp @@ -0,0 +1,28 @@ +#include "storm/counterexamples/HighLevelCounterexample.h" + +namespace storm { + namespace counterexamples { + + HighLevelCounterexample::HighLevelCounterexample(storm::storage::SymbolicModelDescription const& model) : model(model) { + // Intentionally left empty. + } + + bool HighLevelCounterexample::isPrismHighLevelCounterexample() const { + return model.isPrismProgram(); + } + + bool HighLevelCounterexample::isJaniHighLevelCounterexample() const { + return model.isJaniModel(); + } + + storm::storage::SymbolicModelDescription const& HighLevelCounterexample::getModelDescription() const { + return model; + } + + void HighLevelCounterexample::writeToStream(std::ostream& out) const { + out << "High-level counterexample: " << std::endl; + out << model; + } + + } +} diff --git a/src/storm/counterexamples/HighLevelCounterexample.h b/src/storm/counterexamples/HighLevelCounterexample.h new file mode 100644 index 000000000..92310a5ba --- /dev/null +++ b/src/storm/counterexamples/HighLevelCounterexample.h @@ -0,0 +1,26 @@ +#pragma once + +#include "storm/counterexamples/Counterexample.h" + +#include "storm/storage/SymbolicModelDescription.h" + +namespace storm { + namespace counterexamples { + + class HighLevelCounterexample : public Counterexample { + public: + HighLevelCounterexample(storm::storage::SymbolicModelDescription const& model); + + void writeToStream(std::ostream& out) const override; + + bool isPrismHighLevelCounterexample() const; + bool isJaniHighLevelCounterexample() const; + + storm::storage::SymbolicModelDescription const& getModelDescription() const; + + private: + storm::storage::SymbolicModelDescription model; + }; + + } +} diff --git a/src/storm/counterexamples/MILPMinimalLabelSetGenerator.h b/src/storm/counterexamples/MILPMinimalLabelSetGenerator.h index 7843bb4b0..da7c55b77 100644 --- a/src/storm/counterexamples/MILPMinimalLabelSetGenerator.h +++ b/src/storm/counterexamples/MILPMinimalLabelSetGenerator.h @@ -1,5 +1,4 @@ -#ifndef STORM_COUNTEREXAMPLES_MILPMINIMALLABELSETGENERATOR_MDP_H_ -#define STORM_COUNTEREXAMPLES_MILPMINIMALLABELSETGENERATOR_MDP_H_ +#pragma once #include #include @@ -18,7 +17,7 @@ #include "storm/solver/MinMaxLinearEquationSolver.h" -#include "storm/counterexamples/PrismHighLevelCounterexample.h" +#include "storm/counterexamples/HighLevelCounterexample.h" #include "storm/utility/graph.h" #include "storm/utility/counterexamples.h" @@ -26,6 +25,7 @@ #include "storm/solver/LpSolver.h" #include "storm/storage/sparse/PrismChoiceOrigins.h" +#include "storm/storage/sparse/JaniChoiceOrigins.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/GeneralSettings.h" @@ -976,12 +976,12 @@ namespace storm { * @param formulaPtr A pointer to a safety formula. The outermost operator must be a probabilistic bound operator with a strict upper bound. The nested * formula can be either an unbounded until formula or an eventually formula. */ - static std::shared_ptr computeCounterexample(Environment const& env, storm::prism::Program const& program, storm::models::sparse::Mdp const& mdp, std::shared_ptr const& formula) { + static std::shared_ptr computeCounterexample(Environment const& env, storm::storage::SymbolicModelDescription const& symbolicModel, storm::models::sparse::Mdp const& mdp, std::shared_ptr const& formula) { std::cout << std::endl << "Generating minimal label counterexample for formula " << *formula << std::endl; // Check whether there are choice origins available STORM_LOG_THROW(mdp.hasChoiceOrigins(), storm::exceptions::InvalidArgumentException, "Restriction to minimal command set is impossible for model without choice origns."); - STORM_LOG_THROW(mdp.getChoiceOrigins()->isPrismChoiceOrigins(), storm::exceptions::InvalidArgumentException, "Restriction to command set is impossible for model without prism choice origins."); + STORM_LOG_THROW(mdp.getChoiceOrigins()->isPrismChoiceOrigins() || mdp.getChoiceOrigins()->isJaniChoiceOrigins(), storm::exceptions::InvalidArgumentException, "Restriction to command set is impossible for model without PRISM or JANI choice origins."); STORM_LOG_THROW(formula->isProbabilityOperatorFormula(), storm::exceptions::InvalidPropertyException, "Counterexample generation does not support this kind of formula. Expecting a probability operator as the outermost formula element."); storm::logic::ProbabilityOperatorFormula const& probabilityOperator = formula->asProbabilityOperatorFormula(); @@ -1020,21 +1020,34 @@ namespace storm { } // Obtain the label sets for each choice. - // The label set of a choice corresponds to the set of prism commands that induce the choice. - storm::storage::sparse::PrismChoiceOrigins const& choiceOrigins = mdp.getChoiceOrigins()->asPrismChoiceOrigins(); - std::vector> labelSets; - labelSets.reserve(mdp.getNumberOfChoices()); - for (uint_fast64_t choice = 0; choice < mdp.getNumberOfChoices(); ++choice) { - labelSets.push_back(choiceOrigins.getCommandSet(choice)); + std::vector> labelSets(mdp.getNumberOfChoices()); + if (mdp.getChoiceOrigins()->isPrismChoiceOrigins()) { + storm::storage::sparse::PrismChoiceOrigins const& choiceOrigins = mdp.getChoiceOrigins()->asPrismChoiceOrigins(); + for (uint_fast64_t choice = 0; choice < mdp.getNumberOfChoices(); ++choice) { + labelSets[choice] = choiceOrigins.getCommandSet(choice); + } + } else { + storm::storage::sparse::JaniChoiceOrigins const& choiceOrigins = mdp.getChoiceOrigins()->asJaniChoiceOrigins(); + + // The choice origins are known to be JANI ones at this point. + for (uint_fast64_t choice = 0; choice < mdp.getNumberOfChoices(); ++choice) { + labelSets[choice] = choiceOrigins.getEdgeIndexSet(choice); + } } // Delegate the actual computation work to the function of equal name. auto startTime = std::chrono::high_resolution_clock::now(); - boost::container::flat_set usedCommandSet = getMinimalLabelSet(env, mdp, labelSets, phiStates, psiStates, threshold, strictBound, true, storm::settings::getModule().isUseSchedulerCutsSet()); + boost::container::flat_set usedLabelSet = getMinimalLabelSet(env, mdp, labelSets, phiStates, psiStates, threshold, strictBound, true, storm::settings::getModule().isUseSchedulerCutsSet()); auto endTime = std::chrono::high_resolution_clock::now(); - std::cout << std::endl << "Computed minimal command set of size " << usedCommandSet.size() << " in " << std::chrono::duration_cast(endTime - startTime).count() << "ms." << std::endl; + std::cout << std::endl << "Computed minimal command set of size " << usedLabelSet.size() << " in " << std::chrono::duration_cast(endTime - startTime).count() << "ms." << std::endl; - return std::make_shared(program.restrictCommands(usedCommandSet)); + + if (symbolicModel.isPrismProgram()) { + return std::make_shared(symbolicModel.asPrismProgram().restrictCommands(usedLabelSet)); + } else { + STORM_LOG_ASSERT(symbolicModel.isJaniModel(), "Unknown symbolic model description type."); + return std::make_shared(symbolicModel.asJaniModel().restrictEdges(usedLabelSet)); + } } }; @@ -1042,4 +1055,3 @@ namespace storm { } // namespace counterexamples } // namespace storm -#endif /* STORM_COUNTEREXAMPLES_MILPMINIMALLABELSETGENERATOR_MDP_H_ */ diff --git a/src/storm/counterexamples/PrismHighLevelCounterexample.cpp b/src/storm/counterexamples/PrismHighLevelCounterexample.cpp deleted file mode 100644 index ae0e7fb06..000000000 --- a/src/storm/counterexamples/PrismHighLevelCounterexample.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "storm/counterexamples/PrismHighLevelCounterexample.h" - -namespace storm { - namespace counterexamples { - - PrismHighLevelCounterexample::PrismHighLevelCounterexample(storm::prism::Program const& program) : program(program) { - // Intentionally left empty. - } - - void PrismHighLevelCounterexample::writeToStream(std::ostream& out) const { - out << "High-level counterexample (PRISM program): " << std::endl; - out << program; - } - - } -} diff --git a/src/storm/counterexamples/PrismHighLevelCounterexample.h b/src/storm/counterexamples/PrismHighLevelCounterexample.h deleted file mode 100644 index 3e3de71c2..000000000 --- a/src/storm/counterexamples/PrismHighLevelCounterexample.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "storm/counterexamples/Counterexample.h" - -#include "storm/storage/prism/Program.h" - -namespace storm { - namespace counterexamples { - - class PrismHighLevelCounterexample : public Counterexample { - public: - PrismHighLevelCounterexample(storm::prism::Program const& program); - - void writeToStream(std::ostream& out) const override; - - private: - storm::prism::Program program; - }; - - } -} diff --git a/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h b/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h index 7210d6e1b..e5b9f039f 100644 --- a/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h +++ b/src/storm/counterexamples/SMTMinimalLabelSetGenerator.h @@ -5,7 +5,7 @@ #include "storm/solver/Z3SmtSolver.h" -#include "storm/counterexamples/PrismHighLevelCounterexample.h" +#include "storm/counterexamples/HighLevelCounterexample.h" #include "storm/storage/prism/Program.h" #include "storm/storage/expressions/Expression.h" @@ -1875,11 +1875,11 @@ namespace storm { return commandSet; } - static std::shared_ptr computeCounterexample(Environment const& env, storm::prism::Program program, storm::models::sparse::Model const& model, std::shared_ptr const& formula) { + static std::shared_ptr computeCounterexample(Environment const& env, storm::prism::Program program, storm::models::sparse::Model const& model, std::shared_ptr const& formula) { #ifdef STORM_HAVE_Z3 auto commandSet = computeCounterexampleCommandSet(env, program, model, formula); - return std::make_shared(program.restrictCommands(commandSet)); + return std::make_shared(program.restrictCommands(commandSet)); #else throw storm::exceptions::NotImplementedException() << "This functionality is unavailable since storm has been compiled without support for Z3."; diff --git a/src/storm/generator/JaniNextStateGenerator.cpp b/src/storm/generator/JaniNextStateGenerator.cpp index 7e49b0771..3217180f6 100644 --- a/src/storm/generator/JaniNextStateGenerator.cpp +++ b/src/storm/generator/JaniNextStateGenerator.cpp @@ -15,6 +15,8 @@ #include "storm/storage/jani/ParallelComposition.h" #include "storm/storage/jani/CompositionInformationVisitor.h" +#include "storm/storage/sparse/JaniChoiceOrigins.h" + #include "storm/builder/jit/Distribution.h" #include "storm/utility/constants.h" @@ -338,6 +340,10 @@ namespace storm { for (uint_fast64_t rewardVariableIndex = 0; rewardVariableIndex < rewardVariables.size(); ++rewardVariableIndex) { stateActionRewards[rewardVariableIndex] += choice.getRewards()[rewardVariableIndex] * choice.getTotalMass() / totalExitRate; } + + if (this->options.isBuildChoiceOriginsSet() && choice.hasOriginData()) { + globalChoice.addOriginData(choice.getOriginData()); + } } globalChoice.addRewards(std::move(stateActionRewards)); @@ -408,7 +414,7 @@ namespace storm { checkGlobalVariableWritesValid(edgeCombination); } - std::vector iteratorList(edgeCombination.size()); + std::vector iteratorList(edgeCombination.size()); // Initialize the list of iterators. for (size_t i = 0; i < edgeCombination.size(); ++i) { @@ -425,8 +431,15 @@ namespace storm { currentDistribution.add(state, storm::utility::one()); + EdgeIndexSet edgeIndices; for (uint_fast64_t i = 0; i < iteratorList.size(); ++i) { - storm::jani::Edge const& edge = **iteratorList[i]; + auto const& indexAndEdge = *iteratorList[i]; + + if (this->getOptions().isBuildChoiceOriginsSet()) { + edgeIndices.insert(model.encodeAutomatonAndEdgeIndices(edgeCombination[i].first, indexAndEdge.first)); + } + + storm::jani::Edge const& edge = *indexAndEdge.second; for (auto const& destination : edge.getDestinations()) { for (auto const& stateProbability : currentDistribution) { @@ -465,6 +478,11 @@ namespace storm { // Now create the actual distribution. Choice& choice = result.back(); + // Add the edge indices if requested. + if (this->getOptions().isBuildChoiceOriginsSet()) { + choice.addOriginData(boost::any(std::move(edgeIndices))); + } + // Add the rewards to the choice. choice.addRewards(std::move(stateActionRewards)); @@ -515,12 +533,17 @@ namespace storm { auto edgesIt = nonsychingEdges.second.find(locations[automatonIndex]); if (edgesIt != nonsychingEdges.second.end()) { - for (auto const& edge : edgesIt->second) { - if (!this->evaluator->asBool(edge->getGuard())) { + for (auto const& indexAndEdge : edgesIt->second) { + if (!this->evaluator->asBool(indexAndEdge.second->getGuard())) { continue; } - Choice choice = expandNonSynchronizingEdge(*edge, outputAndEdges.first ? outputAndEdges.first.get() : edge->getActionIndex(), automatonIndex, state, stateToIdCallback); + Choice choice = expandNonSynchronizingEdge(*indexAndEdge.second, outputAndEdges.first ? outputAndEdges.first.get() : indexAndEdge.second->getActionIndex(), automatonIndex, state, stateToIdCallback); + + if (this->getOptions().isBuildChoiceOriginsSet()) { + EdgeIndexSet edgeIndex { model.encodeAutomatonAndEdgeIndices(automatonIndex, indexAndEdge.first) }; + choice.addOriginData(boost::any(std::move(edgeIndex))); + } result.emplace_back(std::move(choice)); } } @@ -534,18 +557,18 @@ namespace storm { bool productiveCombination = true; for (auto const& automatonAndEdges : outputAndEdges.second) { uint64_t automatonIndex = automatonAndEdges.first; - EdgeSet enabledEdgesOfAutomaton; + EdgeSetWithIndices enabledEdgesOfAutomaton; bool atLeastOneEdge = false; auto edgesIt = automatonAndEdges.second.find(locations[automatonIndex]); if (edgesIt != automatonAndEdges.second.end()) { - for (auto const& edge : edgesIt->second) { - if (!this->evaluator->asBool(edge->getGuard())) { + for (auto const& indexAndEdge : edgesIt->second) { + if (!this->evaluator->asBool(indexAndEdge.second->getGuard())) { continue; } atLeastOneEdge = true; - enabledEdgesOfAutomaton.emplace_back(edge); + enabledEdgesOfAutomaton.emplace_back(indexAndEdge); } } @@ -560,7 +583,7 @@ namespace storm { if (productiveCombination) { std::vector> choices = expandSynchronizingEdgeCombination(automataEdgeSets, outputActionIndex, state, stateToIdCallback); - + for (auto const& choice : choices) { result.emplace_back(std::move(choice)); } @@ -575,8 +598,8 @@ namespace storm { void JaniNextStateGenerator::checkGlobalVariableWritesValid(AutomataEdgeSets const& enabledEdges) const { std::map writtenGlobalVariables; for (auto edgeSetIt = enabledEdges.begin(), edgeSetIte = enabledEdges.end(); edgeSetIt != edgeSetIte; ++edgeSetIt) { - for (auto const& edge : edgeSetIt->second) { - for (auto const& globalVariable : edge->getWrittenGlobalVariables()) { + for (auto const& indexAndEdge : edgeSetIt->second) { + for (auto const& globalVariable : indexAndEdge.second->getWrittenGlobalVariables()) { auto it = writtenGlobalVariables.find(globalVariable); auto index = std::distance(enabledEdges.begin(), edgeSetIt); @@ -707,8 +730,10 @@ namespace storm { this->parallelAutomata.push_back(automaton); LocationsAndEdges locationsAndEdges; + uint64_t edgeIndex = 0; for (auto const& edge : automaton.getEdges()) { - locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(&edge); + locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(std::make_pair(edgeIndex, &edge)); + ++edgeIndex; } AutomataAndEdges automataAndEdges; @@ -726,10 +751,12 @@ namespace storm { // Add edges with silent action. LocationsAndEdges locationsAndEdges; + uint64_t edgeIndex = 0; for (auto const& edge : parallelAutomata.back().get().getEdges()) { if (edge.getActionIndex() == storm::jani::Model::SILENT_ACTION_INDEX) { - locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(&edge); + locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(std::make_pair(edgeIndex, &edge)); } + ++edgeIndex; } if (!locationsAndEdges.empty()) { @@ -750,10 +777,12 @@ namespace storm { if (!storm::jani::SynchronizationVector::isNoActionInput(element)) { LocationsAndEdges locationsAndEdges; uint64_t actionIndex = this->model.getActionIndex(element); + uint64_t edgeIndex = 0; for (auto const& edge : parallelAutomata[automatonIndex].get().getEdges()) { if (edge.getActionIndex() == actionIndex) { - locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(&edge); + locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(std::make_pair(edgeIndex, &edge)); } + ++edgeIndex; } if (locationsAndEdges.empty()) { atLeastOneEdge = false; @@ -773,6 +802,39 @@ namespace storm { STORM_LOG_TRACE("Number of synchronizations: " << this->edges.size() << "."); } + template + std::shared_ptr JaniNextStateGenerator::generateChoiceOrigins(std::vector& dataForChoiceOrigins) const { + if (!this->getOptions().isBuildChoiceOriginsSet()) { + return nullptr; + } + + std::vector identifiers; + identifiers.reserve(dataForChoiceOrigins.size()); + + std::map edgeIndexSetToIdentifierMap; + // The empty edge set (i.e., the choices without origin) always has to get identifier getIdentifierForChoicesWithNoOrigin() -- which is assumed to be 0 + STORM_LOG_ASSERT(storm::storage::sparse::ChoiceOrigins::getIdentifierForChoicesWithNoOrigin() == 0, "The no origin identifier is assumed to be zero"); + edgeIndexSetToIdentifierMap.insert(std::make_pair(EdgeIndexSet(), 0)); + uint_fast64_t currentIdentifier = 1; + for (boost::any& originData : dataForChoiceOrigins) { + STORM_LOG_ASSERT(originData.empty() || boost::any_cast(&originData) != nullptr, "Origin data has unexpected type: " << originData.type().name() << "."); + + EdgeIndexSet currentEdgeIndexSet = originData.empty() ? EdgeIndexSet() : boost::any_cast(std::move(originData)); + auto insertionRes = edgeIndexSetToIdentifierMap.emplace(std::move(currentEdgeIndexSet), currentIdentifier); + identifiers.push_back(insertionRes.first->second); + if (insertionRes.second) { + ++currentIdentifier; + } + } + + std::vector identifierToEdgeIndexSetMapping(currentIdentifier); + for (auto const& setIdPair : edgeIndexSetToIdentifierMap) { + identifierToEdgeIndexSetMapping[setIdPair.second] = setIdPair.first; + } + + return std::make_shared(std::make_shared(model), std::move(identifiers), std::move(identifierToEdgeIndexSetMapping)); + } + template void JaniNextStateGenerator::checkValid() const { // If the program still contains undefined constants and we are not in a parametric setting, assemble an appropriate error message. diff --git a/src/storm/generator/JaniNextStateGenerator.h b/src/storm/generator/JaniNextStateGenerator.h index 594efc91c..bd6b0c922 100644 --- a/src/storm/generator/JaniNextStateGenerator.h +++ b/src/storm/generator/JaniNextStateGenerator.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "storm/generator/NextStateGenerator.h" #include "storm/storage/jani/Model.h" @@ -17,6 +19,7 @@ namespace storm { class JaniNextStateGenerator : public NextStateGenerator { public: typedef typename NextStateGenerator::StateToIdCallback StateToIdCallback; + typedef boost::container::flat_set EdgeIndexSet; JaniNextStateGenerator(storm::jani::Model const& model, NextStateGeneratorOptions const& options = NextStateGeneratorOptions()); @@ -32,6 +35,8 @@ namespace storm { virtual storm::models::sparse::StateLabeling label(storm::storage::sparse::StateStorage const& stateStorage, std::vector const& initialStateIndices = {}, std::vector const& deadlockStateIndices = {}) override; + virtual std::shared_ptr generateChoiceOrigins(std::vector& dataForChoiceOrigins) const override; + private: /*! * Retrieves the location index from the given state. @@ -79,12 +84,12 @@ namespace storm { */ Choice expandNonSynchronizingEdge(storm::jani::Edge const& edge, uint64_t outputActionIndex, uint64_t automatonIndex, CompressedState const& state, StateToIdCallback stateToIdCallback); - typedef std::vector EdgeSet; - typedef std::unordered_map LocationsAndEdges; + typedef std::vector> EdgeSetWithIndices; + typedef std::unordered_map LocationsAndEdges; typedef std::vector> AutomataAndEdges; typedef std::pair, AutomataAndEdges> OutputAndEdges; - typedef std::pair AutomatonAndEdgeSet; + typedef std::pair AutomatonAndEdgeSet; typedef std::vector AutomataEdgeSets; std::vector> expandSynchronizingEdgeCombination(AutomataEdgeSets const& edgeCombination, uint64_t outputActionIndex, CompressedState const& state, StateToIdCallback stateToIdCallback); diff --git a/src/storm/generator/PrismNextStateGenerator.cpp b/src/storm/generator/PrismNextStateGenerator.cpp index f9cb0a102..e5f4dc4e1 100644 --- a/src/storm/generator/PrismNextStateGenerator.cpp +++ b/src/storm/generator/PrismNextStateGenerator.cpp @@ -601,7 +601,6 @@ namespace storm { template std::shared_ptr PrismNextStateGenerator::generateChoiceOrigins(std::vector& dataForChoiceOrigins) const { - if (!this->getOptions().isBuildChoiceOriginsSet()) { return nullptr; } diff --git a/src/storm/storage/SymbolicModelDescription.cpp b/src/storm/storage/SymbolicModelDescription.cpp index e602e9300..b08166125 100644 --- a/src/storm/storage/SymbolicModelDescription.cpp +++ b/src/storm/storage/SymbolicModelDescription.cpp @@ -178,5 +178,16 @@ namespace storm { storm::utility::prism::requireNoUndefinedConstants(this->asPrismProgram()); } } + + std::ostream& operator<<(std::ostream& out, SymbolicModelDescription const& model) { + if (model.isPrismProgram()) { + out << model.asPrismProgram(); + } else if (model.isJaniModel()) { + out << model.asJaniModel(); + } else { + out << "unkown symbolic model description"; + } + return out; + } } } diff --git a/src/storm/storage/SymbolicModelDescription.h b/src/storm/storage/SymbolicModelDescription.h index 05daa5ec2..137f1a7d4 100644 --- a/src/storm/storage/SymbolicModelDescription.h +++ b/src/storm/storage/SymbolicModelDescription.h @@ -52,5 +52,7 @@ namespace storm { boost::optional> modelDescription; }; + std::ostream& operator<<(std::ostream& out, SymbolicModelDescription const& model); + } } diff --git a/src/storm/storage/jani/Automaton.cpp b/src/storm/storage/jani/Automaton.cpp index 920854628..6c06ed096 100644 --- a/src/storm/storage/jani/Automaton.cpp +++ b/src/storm/storage/jani/Automaton.cpp @@ -301,8 +301,6 @@ namespace storm { return ConstEdges(it1, it2); } - - void Automaton::addEdge(Edge const& edge) { STORM_LOG_THROW(edge.getSourceLocationIndex() < locations.size(), storm::exceptions::InvalidArgumentException, "Cannot add edge with unknown source location index '" << edge.getSourceLocationIndex() << "'."); @@ -529,6 +527,20 @@ namespace storm { return result; } + void Automaton::restrictToEdges(boost::container::flat_set const& edgeIndices) { + std::vector oldEdges = this->edges; + + this->edges.clear(); + actionIndices.clear(); + for (auto& e : locationToStartingIndex) { + e = 0; + } + + for (auto const& index : edgeIndices) { + this->addEdge(oldEdges[index]); + } + } + void Automaton::writeDotToStream(std::ostream& outStream, std::vector const& actionNames) const { outStream << "\tsubgraph " << name << " {" << std::endl; diff --git a/src/storm/storage/jani/Automaton.h b/src/storm/storage/jani/Automaton.h index 37b4e9755..2643aa0fc 100644 --- a/src/storm/storage/jani/Automaton.h +++ b/src/storm/storage/jani/Automaton.h @@ -374,6 +374,11 @@ namespace storm { void writeDotToStream(std::ostream& outStream, std::vector const& actionNames) const; + /*! + * Restricts the automaton to the edges given by the indices. All other edges are deleted. + */ + void restrictToEdges(boost::container::flat_set const& edgeIndices); + private: /// The name of the automaton. std::string name; diff --git a/src/storm/storage/jani/Edge.h b/src/storm/storage/jani/Edge.h index af98706ae..5585296e6 100644 --- a/src/storm/storage/jani/Edge.h +++ b/src/storm/storage/jani/Edge.h @@ -110,7 +110,7 @@ namespace storm { * @param localVars */ void simplifyIndexedAssignments(VariableSet const& localVars); - + std::shared_ptr const& getTemplateEdge(); private: diff --git a/src/storm/storage/jani/Model.cpp b/src/storm/storage/jani/Model.cpp index 27232786b..37031bb4b 100644 --- a/src/storm/storage/jani/Model.cpp +++ b/src/storm/storage/jani/Model.cpp @@ -15,6 +15,7 @@ #include "storm/storage/jani/ParallelComposition.h" #include "storm/storage/jani/CompositionInformationVisitor.h" #include "storm/storage/jani/Compositions.h" +#include "storm/storage/jani/JSONExporter.h" #include "storm/storage/expressions/LinearityCheckVisitor.h" @@ -1154,7 +1155,36 @@ namespace storm { } return false; } + + uint64_t Model::encodeAutomatonAndEdgeIndices(uint64_t automatonIndex, uint64_t edgeIndex) const { + return automatonIndex << 32 | edgeIndex; + } + + std::pair Model::decodeAutomatonAndEdgeIndices(uint64_t index) const { + return std::make_pair(index >> 32, index & ((1ull << 32) - 1)); + } + Model Model::restrictEdges(boost::container::flat_set const& automataAndEdgeIndices) const { + Model result(*this); + + // Restrict all automata. + for (uint64_t automatonIndex = 0; automatonIndex < result.automata.size(); ++automatonIndex) { + + // Compute the set of edges that is to be kept for this automaton. + boost::container::flat_set automatonEdgeIndices; + for (auto const& e : automataAndEdgeIndices) { + auto automatonAndEdgeIndex = decodeAutomatonAndEdgeIndices(e); + if (automatonAndEdgeIndex.first == automatonIndex) { + automatonEdgeIndices.insert(automatonAndEdgeIndex.second); + } + } + + result.automata[automatonIndex].restrictToEdges(automatonEdgeIndices); + } + + return result; + } + Model Model::createModelFromAutomaton(Automaton const& automaton) const { // Copy the full model Model newModel(*this); @@ -1194,5 +1224,10 @@ namespace storm { outStream << "}"; } + + std::ostream& operator<<(std::ostream& out, Model const& model) { + JsonExporter::toStream(model, std::vector(), out); + return out; + } } } diff --git a/src/storm/storage/jani/Model.h b/src/storm/storage/jani/Model.h index bb0b48f26..f45f78b29 100644 --- a/src/storm/storage/jani/Model.h +++ b/src/storm/storage/jani/Model.h @@ -449,6 +449,18 @@ namespace storm { */ bool reusesActionsInComposition() const; + /*! + * Encode and decode a tuple of automaton and edge index in one 64-bit index. + */ + uint64_t encodeAutomatonAndEdgeIndices(uint64_t automatonIndex, uint64_t edgeIndex) const; + std::pair decodeAutomatonAndEdgeIndices(uint64_t index) const; + + /*! + * Creates a new model that only contains the selected edges. The edge indices encode the automata and + * (local) indices of the edges within the automata. + */ + Model restrictEdges(boost::container::flat_set const& automataAndEdgeIndices) const; + void writeDotToStream(std::ostream& outStream = std::cout) const; /// The name of the silent action. @@ -505,6 +517,8 @@ namespace storm { // The expression restricting the legal initial values of the global variables. storm::expressions::Expression initialStatesRestriction; }; + + std::ostream& operator<<(std::ostream& out, Model const& model); } } diff --git a/src/storm/storage/sparse/JaniChoiceOrigins.cpp b/src/storm/storage/sparse/JaniChoiceOrigins.cpp index 4fce08388..d012c4418 100644 --- a/src/storm/storage/sparse/JaniChoiceOrigins.cpp +++ b/src/storm/storage/sparse/JaniChoiceOrigins.cpp @@ -1,14 +1,16 @@ #include "storm/storage/sparse/JaniChoiceOrigins.h" +#include "storm/storage/jani/Model.h" + #include "storm/utility/macros.h" +#include "storm/exceptions/InvalidArgumentException.h" namespace storm { namespace storage { namespace sparse { - JaniChoiceOrigins::JaniChoiceOrigins(std::vector const& indexToIdentifierMapping) : ChoiceOrigins(indexToIdentifierMapping) { - // Intentionally left empty - STORM_LOG_ASSERT(false, "Jani choice origins not properly implemented"); + JaniChoiceOrigins::JaniChoiceOrigins(std::shared_ptr const& janiModel, std::vector const& indexToIdentifierMapping, std::vector const& identifierToEdgeIndexSetMapping) : ChoiceOrigins(indexToIdentifierMapping), model(janiModel), identifierToEdgeIndexSet(identifierToEdgeIndexSetMapping) { + STORM_LOG_THROW(identifierToEdgeIndexSet[this->getIdentifierForChoicesWithNoOrigin()].empty(), storm::exceptions::InvalidArgumentException, "The given edge set for the choices without origin is non-empty"); } bool JaniChoiceOrigins::isJaniChoiceOrigins() const { @@ -16,17 +18,27 @@ namespace storm { } uint_fast64_t JaniChoiceOrigins::getNumberOfIdentifiers() const { - return 0; + return identifierToEdgeIndexSet.size(); + } + + storm::jani::Model const& JaniChoiceOrigins::getModel() const { + return *model; } - + + JaniChoiceOrigins::EdgeIndexSet const& JaniChoiceOrigins::getEdgeIndexSet(uint_fast64_t choiceIndex) const { + return identifierToEdgeIndexSet[this->getIdentifier(choiceIndex)]; + } + std::shared_ptr JaniChoiceOrigins::cloneWithNewIndexToIdentifierMapping(std::vector&& indexToIdentifierMapping) const { - return std::make_shared(std::move(indexToIdentifierMapping)); + auto result = std::make_shared(this->model, std::move(indexToIdentifierMapping), std::move(identifierToEdgeIndexSet)); + result->identifierToInfo = this->identifierToInfo; + return result; } void JaniChoiceOrigins::computeIdentifierInfos() const { - + STORM_LOG_ASSERT(false, "Jani choice origins not properly implemented"); } } } -} \ No newline at end of file +} diff --git a/src/storm/storage/sparse/JaniChoiceOrigins.h b/src/storm/storage/sparse/JaniChoiceOrigins.h index 3f0d09207..9e4de090b 100644 --- a/src/storm/storage/sparse/JaniChoiceOrigins.h +++ b/src/storm/storage/sparse/JaniChoiceOrigins.h @@ -3,25 +3,30 @@ #include #include -#include "storm/storage/sparse/ChoiceOrigins.h" +#include +#include "storm/storage/sparse/ChoiceOrigins.h" namespace storm { + namespace jani { + class Model; + } + namespace storage { namespace sparse { - /*! * This class represents for each choice the origin in the jani specification * // TODO complete this */ class JaniChoiceOrigins : public ChoiceOrigins { public: - + typedef boost::container::flat_set EdgeIndexSet; + /*! * Creates a new representation of the choice indices to their origin in the Jani specification */ - JaniChoiceOrigins(std::vector const& indexToIdentifierMapping); + JaniChoiceOrigins(std::shared_ptr const& janiModel, std::vector const& indexToIdentifierMapping, std::vector const& identifierToEdgeIndexSetMapping); virtual ~JaniChoiceOrigins() = default; @@ -33,6 +38,18 @@ namespace storm { */ virtual uint_fast64_t getNumberOfIdentifiers() const override; + /*! + * Retrieves the associated JANI model. + */ + storm::jani::Model const& getModel() const; + + /* + * Returns the set of edges that induced the choice with the given index. + * The edges set is represented by a set of indices that encode an automaton index and its local edge index. + */ + EdgeIndexSet const& getEdgeIndexSet(uint_fast64_t choiceIndex) const; + + private: /* * Returns a copy of this object where the mapping of choice indices to origin identifiers is replaced by the given one. */ @@ -43,9 +60,9 @@ namespace storm { */ virtual void computeIdentifierInfos() const override; - private: - + std::shared_ptr model; + std::vector identifierToEdgeIndexSet; }; } } -} \ No newline at end of file +} diff --git a/src/storm/storage/sparse/PrismChoiceOrigins.cpp b/src/storm/storage/sparse/PrismChoiceOrigins.cpp index 366b9524b..69084f8cd 100644 --- a/src/storm/storage/sparse/PrismChoiceOrigins.cpp +++ b/src/storm/storage/sparse/PrismChoiceOrigins.cpp @@ -35,8 +35,7 @@ namespace storm { } std::shared_ptr PrismChoiceOrigins::cloneWithNewIndexToIdentifierMapping(std::vector&& indexToIdentifierMapping) const { - std::vector identifierToCommandSetMapping = this->identifierToCommandSet; - auto result = std::make_shared(this->program, std::move(indexToIdentifierMapping), std::move(identifierToCommandSetMapping)); + auto result = std::make_shared(this->program, std::move(indexToIdentifierMapping), std::move(this->identifierToCommandSet)); result->identifierToInfo = this->identifierToInfo; return result; } diff --git a/src/storm/storage/sparse/PrismChoiceOrigins.h b/src/storm/storage/sparse/PrismChoiceOrigins.h index 2a004d633..f2a6af716 100644 --- a/src/storm/storage/sparse/PrismChoiceOrigins.h +++ b/src/storm/storage/sparse/PrismChoiceOrigins.h @@ -32,7 +32,7 @@ namespace storm { virtual ~PrismChoiceOrigins() = default; - virtual bool isPrismChoiceOrigins() const override ; + virtual bool isPrismChoiceOrigins() const override; /* * Returns the number of identifier that are used by this object.