#include "src/builder/DdJaniModelBuilder.h" #include #include #include "src/logic/Formulas.h" #include "src/storage/jani/Model.h" #include "src/storage/jani/RenameComposition.h" #include "src/storage/jani/AutomatonComposition.h" #include "src/storage/jani/ParallelComposition.h" #include "src/storage/dd/Add.h" #include "src/storage/dd/Bdd.h" #include "src/adapters/AddExpressionAdapter.h" #include "src/models/symbolic/Dtmc.h" #include "src/models/symbolic/Ctmc.h" #include "src/models/symbolic/Mdp.h" #include "src/models/symbolic/StandardRewardModel.h" #include "src/settings/SettingsManager.h" #include "src/settings/modules/MarkovChainSettings.h" #include "src/utility/macros.h" #include "src/utility/jani.h" #include "src/utility/dd.h" #include "src/utility/math.h" #include "src/exceptions/WrongFormatException.h" #include "src/exceptions/InvalidArgumentException.h" #include "src/exceptions/InvalidStateException.h" namespace storm { namespace builder { template DdJaniModelBuilder::Options::Options() : buildAllRewardModels(true), rewardModelsToBuild(), constantDefinitions(), terminalStates(), negatedTerminalStates() { // Intentionally left empty. } template DdJaniModelBuilder::Options::Options(storm::logic::Formula const& formula) : buildAllRewardModels(false), rewardModelsToBuild(), constantDefinitions(), terminalStates(), negatedTerminalStates() { this->preserveFormula(formula); this->setTerminalStatesFromFormula(formula); } template DdJaniModelBuilder::Options::Options(std::vector> const& formulas) : buildAllRewardModels(false), rewardModelsToBuild(), constantDefinitions(), terminalStates(), negatedTerminalStates() { if (formulas.empty()) { this->buildAllRewardModels = true; } else { for (auto const& formula : formulas) { this->preserveFormula(*formula); } if (formulas.size() == 1) { this->setTerminalStatesFromFormula(*formulas.front()); } } } template void DdJaniModelBuilder::Options::preserveFormula(storm::logic::Formula const& formula) { // If we already had terminal states, we need to erase them. if (terminalStates) { terminalStates.reset(); } if (negatedTerminalStates) { negatedTerminalStates.reset(); } // If we are not required to build all reward models, we determine the reward models we need to build. if (!buildAllRewardModels) { std::set referencedRewardModels = formula.getReferencedRewardModels(); rewardModelsToBuild.insert(referencedRewardModels.begin(), referencedRewardModels.end()); } } template void DdJaniModelBuilder::Options::setTerminalStatesFromFormula(storm::logic::Formula const& formula) { if (formula.isAtomicExpressionFormula()) { terminalStates = formula.asAtomicExpressionFormula().getExpression(); } else if (formula.isEventuallyFormula()) { storm::logic::Formula const& sub = formula.asEventuallyFormula().getSubformula(); if (sub.isAtomicExpressionFormula() || sub.isAtomicLabelFormula()) { this->setTerminalStatesFromFormula(sub); } } else if (formula.isUntilFormula()) { storm::logic::Formula const& right = formula.asUntilFormula().getRightSubformula(); if (right.isAtomicExpressionFormula() || right.isAtomicLabelFormula()) { this->setTerminalStatesFromFormula(right); } storm::logic::Formula const& left = formula.asUntilFormula().getLeftSubformula(); if (left.isAtomicExpressionFormula()) { negatedTerminalStates = left.asAtomicExpressionFormula().getExpression(); } } else if (formula.isProbabilityOperatorFormula()) { storm::logic::Formula const& sub = formula.asProbabilityOperatorFormula().getSubformula(); if (sub.isEventuallyFormula() || sub.isUntilFormula()) { this->setTerminalStatesFromFormula(sub); } } } template void DdJaniModelBuilder::Options::addConstantDefinitionsFromString(storm::jani::Model const& model, std::string const& constantDefinitionString) { std::map newConstantDefinitions = storm::utility::jani::parseConstantDefinitionString(model, constantDefinitionString); // If there is at least one constant that is defined, and the constant definition map does not yet exist, // we need to create it. if (!constantDefinitions && !newConstantDefinitions.empty()) { constantDefinitions = std::map(); } // Now insert all the entries that need to be defined. for (auto const& entry : newConstantDefinitions) { constantDefinitions.get().insert(entry); } } template DdJaniModelBuilder::DdJaniModelBuilder(storm::jani::Model const& model, Options const& options) : options(options) { if (options.constantDefinitions) { this->model = model.defineUndefinedConstants(options.constantDefinitions.get()); } else { this->model = model; } if (this->model->hasUndefinedConstants()) { std::vector> undefinedConstants = this->model->getUndefinedConstants(); std::vector strings; for (auto const& constant : undefinedConstants) { std::stringstream stream; stream << constant.get().getName() << " (" << constant.get().getType() << ")"; strings.push_back(stream.str()); } STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Program still contains these undefined constants: " << boost::join(strings, ", ") << "."); } this->model = this->model->substituteConstants(); } template struct CompositionVariables { CompositionVariables() : manager(std::make_shared>()), variableToRowMetaVariableMap(std::make_shared>()), rowExpressionAdapter(std::make_shared>(manager, variableToRowMetaVariableMap)), variableToColumnMetaVariableMap(std::make_shared>()), columnExpressionAdapter(std::make_shared>(manager, variableToColumnMetaVariableMap)) { // Intentionally left empty. } std::shared_ptr> manager; // The meta variables for the row encoding. std::set rowMetaVariables; std::shared_ptr> variableToRowMetaVariableMap; std::shared_ptr> rowExpressionAdapter; // The meta variables for the column encoding. std::set columnMetaVariables; std::shared_ptr> variableToColumnMetaVariableMap; std::shared_ptr> columnExpressionAdapter; // All pairs of row/column meta variables. std::vector> rowColumnMetaVariablePairs; // A mapping from automata to the meta variable encoding their location. std::map> automatonToLocationVariableMap; // A mapping from action indices to the meta variables used to encode these actions. std::map actionVariablesMap; // The meta variables used to encode the remaining nondeterminism. std::vector localNondeterminismVariables; // The meta variables used to encode the actions and nondeterminism. std::set allNondeterminismVariables; // DDs representing the identity for each variable. std::map> variableToIdentityMap; // DDs representing the ranges of each variable. std::map> variableToRangeMap; // A set of all meta variables that correspond to global variables. std::set allGlobalVariables; // DDs representing the identity for each automaton. std::map> automatonToIdentityMap; // DDs representing the valid ranges of the variables of each automaton. std::map> automatonToRangeMap; // A DD representing the valid ranges of the global variables. storm::dd::Add globalVariableRanges; }; // A class responsible for creating the necessary variables for a subsequent composition of automata. template class CompositionVariableCreator : public storm::jani::CompositionVisitor { public: CompositionVariableCreator(storm::jani::Model const& model) : model(model) { // Intentionally left empty. } CompositionVariables create() { // First, check whether every automaton appears exactly once in the system composition. automata.clear(); this->model.getSystemComposition().accept(*this, boost::none); STORM_LOG_THROW(automata.size() == this->model.getNumberOfAutomata(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model whose system composition refers to a subset of automata."); // Then, check that the model does not contain unbounded integer variables. STORM_LOG_THROW(!this->model.getGlobalVariables().containsUnboundedIntegerVariables(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model that contains global unbounded integer variables."); for (auto const& automaton : this->model.getAutomata()) { STORM_LOG_THROW(!automaton.getVariables().containsUnboundedIntegerVariables(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model that contains unbounded integer variables in automaton '" << automaton.getName() << "'."); } // Based on this assumption, we create the variables. return createVariables(); } boost::any visit(storm::jani::AutomatonComposition const& composition, boost::any const& data) override { auto it = automata.find(composition.getAutomatonName()); STORM_LOG_THROW(it == automata.end(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model whose system composition that refers to the automaton '" << composition.getAutomatonName() << "' multiple times."); automata.insert(it, composition.getAutomatonName()); return boost::none; } boost::any visit(storm::jani::RenameComposition const& composition, boost::any const& data) override { composition.getSubcomposition().accept(*this, boost::none); return boost::none; } boost::any visit(storm::jani::ParallelComposition const& composition, boost::any const& data) override { composition.getLeftSubcomposition().accept(*this, boost::none); composition.getRightSubcomposition().accept(*this, boost::none); return boost::none; } private: CompositionVariables createVariables() { CompositionVariables result; for (auto const& action : this->model.getActions()) { if (this->model.getActionIndex(action.getName()) != this->model.getSilentActionIndex()) { std::pair variablePair = result.manager->addMetaVariable(action.getName()); result.actionVariablesMap[this->model.getActionIndex(action.getName())] = variablePair.first; result.allNondeterminismVariables.insert(variablePair.first); } } // FIXME: check how many nondeterminism variables we should actually allocate. uint64_t numberOfNondeterminismVariables = this->model.getNumberOfAutomata(); for (auto const& automaton : this->model.getAutomata()) { numberOfNondeterminismVariables += automaton.getNumberOfEdges(); } for (uint_fast64_t i = 0; i < numberOfNondeterminismVariables; ++i) { std::pair variablePair = result.manager->addMetaVariable("nondet" + std::to_string(i)); result.localNondeterminismVariables.push_back(variablePair.first); result.allNondeterminismVariables.insert(variablePair.first); } for (auto const& automaton : this->model.getAutomata()) { // Start by creating a meta variable for the location of the automaton. std::pair variablePair = result.manager->addMetaVariable("l_" + automaton.getName(), 0, automaton.getNumberOfLocations() - 1); result.automatonToLocationVariableMap[automaton.getName()] = variablePair; // Add the location variable to the row/column variables. result.rowMetaVariables.insert(variablePair.first); result.columnMetaVariables.insert(variablePair.second); // Add the legal range for the location variables. result.variableToRangeMap.emplace(variablePair.first, result.manager->getRange(variablePair.first)); result.variableToRangeMap.emplace(variablePair.second, result.manager->getRange(variablePair.second)); } // Create global variables. storm::dd::Bdd globalVariableRanges = result.manager->getBddOne(); for (auto const& variable : this->model.getGlobalVariables()) { createVariable(variable, result); globalVariableRanges &= result.manager->getRange(result.variableToRowMetaVariableMap->at(variable.getExpressionVariable())); } result.globalVariableRanges = globalVariableRanges.template toAdd(); // Create the variables for the individual automata. for (auto const& automaton : this->model.getAutomata()) { storm::dd::Bdd identity = result.manager->getBddOne(); storm::dd::Bdd range = result.manager->getBddOne(); // Add the identity and ranges of the location variables to the ones of the automaton. std::pair const& locationVariables = result.automatonToLocationVariableMap[automaton.getName()]; storm::dd::Add variableIdentity = result.manager->template getIdentity(locationVariables.first).equals(result.manager->template getIdentity(locationVariables.second)).template toAdd() * result.manager->getRange(locationVariables.first).template toAdd() * result.manager->getRange(locationVariables.second).template toAdd(); identity &= variableIdentity.toBdd(); range &= result.manager->getRange(locationVariables.first); // Then create variables for the variables of the automaton. for (auto const& variable : automaton.getVariables()) { createVariable(variable, result); identity &= result.variableToIdentityMap.at(variable.getExpressionVariable()).toBdd(); range &= result.manager->getRange(result.variableToRowMetaVariableMap->at(variable.getExpressionVariable())); } result.automatonToIdentityMap[automaton.getName()] = identity.template toAdd(); result.automatonToRangeMap[automaton.getName()] = (range && globalVariableRanges).template toAdd(); } return result; } void createVariable(storm::jani::Variable const& variable, CompositionVariables& result) { if (variable.isBooleanVariable()) { createVariable(variable.asBooleanVariable(), result); } else if (variable.isBoundedIntegerVariable()) { createVariable(variable.asBoundedIntegerVariable(), result); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Invalid type of variable in JANI model."); } } void createVariable(storm::jani::BoundedIntegerVariable const& variable, CompositionVariables& result) { int_fast64_t low = variable.getLowerBound().evaluateAsInt(); int_fast64_t high = variable.getUpperBound().evaluateAsInt(); std::pair variablePair = result.manager->addMetaVariable(variable.getName(), low, high); STORM_LOG_TRACE("Created meta variables for global integer variable: " << variablePair.first.getName() << "] and " << variablePair.second.getName() << "."); result.rowMetaVariables.insert(variablePair.first); result.variableToRowMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.first); result.columnMetaVariables.insert(variablePair.second); result.variableToColumnMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.second); storm::dd::Add variableIdentity = result.manager->template getIdentity(variablePair.first).equals(result.manager->template getIdentity(variablePair.second)).template toAdd() * result.manager->getRange(variablePair.first).template toAdd() * result.manager->getRange(variablePair.second).template toAdd(); result.variableToIdentityMap.emplace(variable.getExpressionVariable(), variableIdentity); result.rowColumnMetaVariablePairs.push_back(variablePair); result.variableToRangeMap.emplace(variablePair.first, result.manager->getRange(variablePair.first)); result.variableToRangeMap.emplace(variablePair.second, result.manager->getRange(variablePair.second)); result.allGlobalVariables.insert(variable.getExpressionVariable()); } void createVariable(storm::jani::BooleanVariable const& variable, CompositionVariables& result) { std::pair variablePair = result.manager->addMetaVariable(variable.getName()); STORM_LOG_TRACE("Created meta variables for global boolean variable: " << variablePair.first.getName() << " and " << variablePair.second.getName() << "."); result.rowMetaVariables.insert(variablePair.first); result.variableToRowMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.first); result.columnMetaVariables.insert(variablePair.second); result.variableToColumnMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.second); storm::dd::Add variableIdentity = result.manager->template getIdentity(variablePair.first).equals(result.manager->template getIdentity(variablePair.second)).template toAdd(); result.variableToIdentityMap.emplace(variable.getExpressionVariable(), variableIdentity); result.variableToRangeMap.emplace(variablePair.first, result.manager->getRange(variablePair.first)); result.variableToRangeMap.emplace(variablePair.second, result.manager->getRange(variablePair.second)); result.rowColumnMetaVariablePairs.push_back(variablePair); result.allGlobalVariables.insert(variable.getExpressionVariable()); } storm::jani::Model const& model; std::set automata; }; template struct ComposerResult { ComposerResult(storm::dd::Add const& transitions, storm::dd::Bdd const& illegalFragment, uint64_t numberOfNondeterminismVariables = 0) : transitions(transitions), illegalFragment(illegalFragment), numberOfNondeterminismVariables(numberOfNondeterminismVariables) { // Intentionally left empty. } storm::dd::Add transitions; storm::dd::Bdd illegalFragment; uint64_t numberOfNondeterminismVariables; }; // A class that is responsible for performing the actual composition. This template class SystemComposer : public storm::jani::CompositionVisitor { public: SystemComposer(storm::jani::Model const& model, CompositionVariables const& variables) : model(model), variables(variables) { // Intentionally left empty. } virtual ComposerResult compose() = 0; protected: // The model that is referred to by the composition. storm::jani::Model const& model; // The variable to use when building an automaton. CompositionVariables const& variables; }; // This structure represents an edge destination. template struct EdgeDestinationDd { EdgeDestinationDd(storm::dd::Add const& transitions, std::set const& writtenGlobalVariables = {}) : transitions(transitions), writtenGlobalVariables(writtenGlobalVariables) { // Intentionally left empty. } storm::dd::Add transitions; std::set writtenGlobalVariables; }; template EdgeDestinationDd buildEdgeDestinationDd(storm::jani::Automaton const& automaton, storm::jani::EdgeDestination const& destination, storm::dd::Add const& guard, CompositionVariables const& variables) { storm::dd::Add transitions = variables.rowExpressionAdapter->translateExpression(destination.getProbability()); STORM_LOG_TRACE("Translating edge destination."); // Iterate over all assignments (boolean and integer) and build the DD for it. std::set assignedVariables; for (auto const& assignment : destination.getAssignments()) { // Record the variable as being written. STORM_LOG_TRACE("Assigning to variable " << variables.variableToRowMetaVariableMap->at(assignment.getExpressionVariable()).getName()); assignedVariables.insert(assignment.getExpressionVariable()); // Translate the written variable. auto const& primedMetaVariable = variables.variableToColumnMetaVariableMap->at(assignment.getExpressionVariable()); storm::dd::Add writtenVariable = variables.manager->template getIdentity(primedMetaVariable); // Translate the expression that is being assigned. storm::dd::Add assignedExpression = variables.rowExpressionAdapter->translateExpression(assignment.getAssignedExpression()); // Combine the assigned expression with the guard. storm::dd::Add result = assignedExpression * guard; // Combine the variable and the assigned expression. result = result.equals(writtenVariable).template toAdd(); result *= guard; // Restrict the transitions to the range of the written variable. result = result * variables.variableToRangeMap.at(primedMetaVariable).template toAdd(); // Combine the assignment DDs. transitions *= result; } // Compute the set of assigned global variables. std::set assignedGlobalVariables; std::set_intersection(assignedVariables.begin(), assignedVariables.end(), variables.allGlobalVariables.begin(), variables.allGlobalVariables.end(), std::inserter(assignedGlobalVariables, assignedGlobalVariables.begin())); // All unassigned boolean variables need to keep their value. for (storm::jani::BooleanVariable const& variable : automaton.getVariables().getBooleanVariables()) { if (assignedVariables.find(variable.getExpressionVariable()) == assignedVariables.end()) { STORM_LOG_TRACE("Multiplying identity of variable " << variable.getName()); transitions *= variables.variableToIdentityMap.at(variable.getExpressionVariable()); } } // All unassigned integer variables need to keep their value. for (storm::jani::BoundedIntegerVariable const& variable : automaton.getVariables().getBoundedIntegerVariables()) { if (assignedVariables.find(variable.getExpressionVariable()) == assignedVariables.end()) { STORM_LOG_TRACE("Multiplying identity of variable " << variable.getName()); transitions *= variables.variableToIdentityMap.at(variable.getExpressionVariable()); } } transitions *= variables.manager->getEncoding(variables.automatonToLocationVariableMap.at(automaton.getName()).second, destination.getLocationId()).template toAdd(); return EdgeDestinationDd(transitions, assignedGlobalVariables); } template storm::dd::Add encodeAction(boost::optional const& actionIndex, CompositionVariables const& variables) { storm::dd::Add encoding = variables.manager->template getAddOne(); for (auto it = variables.actionVariablesMap.rbegin(), ite = variables.actionVariablesMap.rend(); it != ite; ++it) { if (actionIndex && it->first == actionIndex.get()) { encoding *= variables.manager->getEncoding(it->second, 1).template toAdd(); } else { encoding *= variables.manager->getEncoding(it->second, 0).template toAdd(); } } return encoding; } template storm::dd::Add encodeIndex(uint64_t index, uint64_t localNondeterminismVariableOffset, uint64_t numberOfLocalNondeterminismVariables, CompositionVariables const& variables) { storm::dd::Add result = variables.manager->template getAddZero(); std::map metaVariableNameToValueMap; for (uint_fast64_t i = 0; i < numberOfLocalNondeterminismVariables; ++i) { if (index & (1ull << (numberOfLocalNondeterminismVariables - i - 1))) { metaVariableNameToValueMap.emplace(variables.localNondeterminismVariables[localNondeterminismVariableOffset + i], 1); } else { metaVariableNameToValueMap.emplace(variables.localNondeterminismVariables[localNondeterminismVariableOffset + i], 0); } } result.setValue(metaVariableNameToValueMap, 1); return result; } template class SeparateEdgesSystemComposer : public SystemComposer { public: // This structure represents an edge. struct EdgeDd { EdgeDd(storm::dd::Add const& guard = storm::dd::Add(), storm::dd::Add const& transitions = storm::dd::Add(), std::set const& writtenGlobalVariables = {}, std::set const& globalVariablesWrittenMultipleTimes = {}) : guard(guard), transitions(transitions), writtenGlobalVariables(writtenGlobalVariables), globalVariablesWrittenMultipleTimes(globalVariablesWrittenMultipleTimes) { // Intentionally left empty. } EdgeDd(EdgeDd const& other) : guard(other.guard), transitions(other.transitions), writtenGlobalVariables(other.writtenGlobalVariables), globalVariablesWrittenMultipleTimes(other.globalVariablesWrittenMultipleTimes) { // Intentionally left empty. } EdgeDd& operator=(EdgeDd const& other) { if (this != &other) { globalVariablesWrittenMultipleTimes = other.globalVariablesWrittenMultipleTimes; writtenGlobalVariables = other.writtenGlobalVariables; guard = other.guard; transitions = other.transitions; } return *this; } storm::dd::Add guard; storm::dd::Add transitions; std::set writtenGlobalVariables; std::set globalVariablesWrittenMultipleTimes; }; // This structure represents a subcomponent of a composition. struct AutomatonDd { AutomatonDd(storm::dd::Add const& identity) : identity(identity) { // Intentionally left empty. } std::map> actionIndexToEdges; storm::dd::Add identity; }; SeparateEdgesSystemComposer(storm::jani::Model const& model, CompositionVariables const& variables) : SystemComposer(model, variables) { // Intentionally left empty. } ComposerResult compose() override { AutomatonDd globalAutomaton = boost::any_cast(this->model.getSystemComposition().accept(*this, boost::none)); return buildSystemFromAutomaton(globalAutomaton); } boost::any visit(storm::jani::AutomatonComposition const& composition, boost::any const& data) override { return buildAutomatonDd(composition.getAutomatonName()); } boost::any visit(storm::jani::RenameComposition const& composition, boost::any const& data) override { AutomatonDd subautomaton = boost::any_cast(composition.getSubcomposition().accept(*this, boost::none)); // Build a mapping from indices to indices for the renaming. std::map renamingIndexToIndex; for (auto const& entry : composition.getRenaming()) { if (this->model.getActionIndex(entry.first) != this->model.getSilentActionIndex()) { // Distinguish the cases where we hide the action or properly rename it. if (entry.second) { renamingIndexToIndex.emplace(this->model.getActionIndex(entry.first), this->model.getActionIndex(entry.second.get())); } else { renamingIndexToIndex.emplace(this->model.getActionIndex(entry.first), this->model.getSilentActionIndex()); } } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Renaming composition must not rename the silent action."); } } // Finally, apply the renaming. AutomatonDd result(subautomaton.identity); for (auto const& actionEdges : subautomaton.actionIndexToEdges) { auto it = renamingIndexToIndex.find(actionEdges.first); if (it != renamingIndexToIndex.end()) { // If we are to rename the action, do so. result.actionIndexToEdges[it->second].insert(result.actionIndexToEdges[it->second].end(), actionEdges.second.begin(), actionEdges.second.end()); } else { // Otherwise copy over the edges. result.actionIndexToEdges[it->first].insert(result.actionIndexToEdges[it->first].begin(), actionEdges.second.begin(), actionEdges.second.end()); } } return result; } boost::any visit(storm::jani::ParallelComposition const& composition, boost::any const& data) override { AutomatonDd leftSubautomaton = boost::any_cast(composition.getLeftSubcomposition().accept(*this, boost::none)); AutomatonDd rightSubautomaton = boost::any_cast(composition.getRightSubcomposition().accept(*this, boost::none)); // Build the set of synchronizing action indices. std::set synchronizingActionIndices; for (auto const& entry : composition.getSynchronizationAlphabet()) { if (this->model.getActionIndex(entry) != this->model.getSilentActionIndex()) { synchronizingActionIndices.insert(this->model.getActionIndex(entry)); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Parallel composition must not synchronize over silent action."); } } // Perform the composition. // First, consider all actions in the left subcomposition. AutomatonDd result(leftSubautomaton.identity * rightSubautomaton.identity); uint64_t index = 0; for (auto const& actionEdges : leftSubautomaton.actionIndexToEdges) { // If we need to synchronize over this action, do so now. if (synchronizingActionIndices.find(actionEdges.first) != synchronizingActionIndices.end()) { auto rightIt = rightSubautomaton.actionIndexToEdges.find(actionEdges.first); if (rightIt != rightSubautomaton.actionIndexToEdges.end()) { for (auto const& edge1 : actionEdges.second) { for (auto const& edge2 : rightIt->second) { EdgeDd edgeDd = composeEdgesInParallel(edge1, edge2); if (!edgeDd.guard.isZero()) { result.actionIndexToEdges[actionEdges.first].push_back(edgeDd); } index++; } } } } else { // Extend all edges by the missing identity (unsynchronizing) and copy them over. for (auto const& edge : actionEdges.second) { result.actionIndexToEdges[actionEdges.first].push_back(extendEdgeWithIdentity(edge, rightSubautomaton.identity)); } } } // Then, consider all actions in the right subcomposition. All synchronizing actions can be ignored, because // we would have dealt with them earlier if there was a suitable synchronization partner. Since there is none, // such transitions can not be taken and we can drop them. for (auto const& actionEdges : rightSubautomaton.actionIndexToEdges) { if (synchronizingActionIndices.find(actionEdges.first) == synchronizingActionIndices.end()) { for (auto const& edge : actionEdges.second) { result.actionIndexToEdges[actionEdges.first].push_back(extendEdgeWithIdentity(edge, leftSubautomaton.identity)); } } } return result; } private: storm::dd::Add combineEdgesBySummation(std::vector const& edges, CompositionVariables const& variables) { storm::dd::Add result = variables.manager->template getAddZero(); for (auto const& edge : edges) { // Simply add all actions, but make sure to include the missing global variable identities. result += edge.transitions * computeMissingGlobalVariableIdentities(edge, variables); } return result; } std::pair> combineEdgesByNondeterministicChoice(std::vector& edges) { // Complete all edges by adding the missing global variable identities. for (auto& edge : edges) { edge.transitions *= computeMissingGlobalVariableIdentities(edge, this->variables); } // Sum all guards, so we can read off the maximal number of nondeterministic choices in any given state. storm::dd::Add sumOfGuards = this->variables.manager->template getAddZero(); for (auto const& edge : edges) { sumOfGuards += edge.guard; } uint_fast64_t maxChoices = static_cast(sumOfGuards.getMax()); STORM_LOG_TRACE("Found " << maxChoices << " local choices."); // Depending on the maximal number of nondeterminstic choices, we need to use some variables to encode the nondeterminism. if (maxChoices == 0) { return std::make_pair(0, this->variables.manager->template getAddZero()); } else if (maxChoices == 1) { return std::make_pair(0, combineEdgesBySummation(edges, this->variables)); } else { // Calculate number of required variables to encode the nondeterminism. uint_fast64_t numberOfBinaryVariables = static_cast(std::ceil(storm::utility::math::log2(maxChoices))); storm::dd::Add allEdges = this->variables.manager->template getAddZero(); storm::dd::Bdd equalsNumberOfChoicesDd; std::vector> choiceDds(maxChoices, this->variables.manager->template getAddZero()); std::vector> remainingDds(maxChoices, this->variables.manager->getBddZero()); for (uint_fast64_t currentChoices = 1; currentChoices <= maxChoices; ++currentChoices) { // Determine the set of states with exactly currentChoices choices. equalsNumberOfChoicesDd = sumOfGuards.equals(this->variables.manager->getConstant(static_cast(currentChoices))); // If there is no such state, continue with the next possible number of choices. if (equalsNumberOfChoicesDd.isZero()) { continue; } // Reset the previously used intermediate storage. for (uint_fast64_t j = 0; j < currentChoices; ++j) { choiceDds[j] = this->variables.manager->template getAddZero(); remainingDds[j] = equalsNumberOfChoicesDd; } for (std::size_t j = 0; j < edges.size(); ++j) { // Check if edge guard overlaps with equalsNumberOfChoicesDd. That is, there are states with exactly currentChoices // choices such that one outgoing choice is given by the j-th edge. storm::dd::Bdd guardChoicesIntersection = edges[j].guard.toBdd() && equalsNumberOfChoicesDd; // If there is no such state, continue with the next command. if (guardChoicesIntersection.isZero()) { continue; } // Split the currentChoices nondeterministic choices. for (uint_fast64_t k = 0; k < currentChoices; ++k) { // Calculate the overlapping part of command guard and the remaining DD. storm::dd::Bdd remainingGuardChoicesIntersection = guardChoicesIntersection && remainingDds[k]; // Check if we can add some overlapping parts to the current index. if (!remainingGuardChoicesIntersection.isZero()) { // Remove overlapping parts from the remaining DD. remainingDds[k] = remainingDds[k] && !remainingGuardChoicesIntersection; // Combine the overlapping part of the guard with command updates and add it to the resulting DD. choiceDds[k] += remainingGuardChoicesIntersection.template toAdd() * edges[j].transitions; } // Remove overlapping parts from the command guard DD guardChoicesIntersection = guardChoicesIntersection && !remainingGuardChoicesIntersection; // If the guard DD has become equivalent to false, we can stop here. if (guardChoicesIntersection.isZero()) { break; } } } // Add the meta variables that encode the nondeterminisim to the different choices. for (uint_fast64_t j = 0; j < currentChoices; ++j) { allEdges += encodeIndex(j, 0, numberOfBinaryVariables, this->variables) * choiceDds[j]; } // Delete currentChoices out of overlapping DD sumOfGuards = sumOfGuards * (!equalsNumberOfChoicesDd).template toAdd(); } return std::make_pair(numberOfBinaryVariables, allEdges); } } storm::dd::Bdd computeIllegalFragmentFromEdges(std::map> const& actionIndexToEdges) { // Create the illegal fragment. For this, we need to find the edges that write global variables multiple times. storm::dd::Bdd illegalFragment = this->variables.manager->getBddZero(); for (auto const& action : actionIndexToEdges) { for (auto const& edge : action.second) { if (!edge.globalVariablesWrittenMultipleTimes.empty()) { for (auto const& variable : edge.globalVariablesWrittenMultipleTimes) { STORM_LOG_WARN("The global variable '" << variable.getName() << "' is written along multiple synchronizing edges. If such a transition is contained in the reachable state space, an error is raised."); illegalFragment |= edge.guard.toBdd(); } } } } return illegalFragment; } ComposerResult buildSystemFromAutomaton(AutomatonDd& automatonDd) { // If the model is an MDP, we need to encode the nondeterminism using additional variables. if (this->model.getModelType() == storm::jani::ModelType::MDP) { std::map>> actionIndexToUsedVariablesAndDd; // Combine the edges of each action individually and keep track of how many local nondeterminism variables // were used. uint64_t highestNumberOfUsedNondeterminismVariables = 0; for (auto& action : automatonDd.actionIndexToEdges) { std::pair> usedVariablesAndDd = combineEdgesByNondeterministicChoice(action.second); actionIndexToUsedVariablesAndDd.emplace(action.first, usedVariablesAndDd); highestNumberOfUsedNondeterminismVariables = std::max(highestNumberOfUsedNondeterminismVariables, usedVariablesAndDd.first); } storm::dd::Add result = this->variables.manager->template getAddZero(); for (auto const& element : actionIndexToUsedVariablesAndDd) { result += element.second.second * encodeAction(element.first != this->model.getSilentActionIndex() ? boost::optional(element.first) : boost::none, this->variables) * encodeIndex(0, element.second.first, highestNumberOfUsedNondeterminismVariables - element.second.first, this->variables); } return ComposerResult(result, computeIllegalFragmentFromEdges(automatonDd.actionIndexToEdges), highestNumberOfUsedNondeterminismVariables); } else if (this->model.getModelType() == storm::jani::ModelType::DTMC || this->model.getModelType() == storm::jani::ModelType::CTMC) { storm::dd::Add result = this->variables.manager->template getAddZero(); for (auto const& action : automatonDd.actionIndexToEdges) { result += combineEdgesBySummation(action.second, this->variables); } return ComposerResult(result, computeIllegalFragmentFromEdges(automatonDd.actionIndexToEdges), 0); } STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Illegal model type."); } storm::dd::Add computeMissingGlobalVariableIdentities(EdgeDd const& edge, CompositionVariables const& variables) { std::set missingIdentities; std::set_difference(variables.allGlobalVariables.begin(), variables.allGlobalVariables.end(), edge.writtenGlobalVariables.begin(), edge.writtenGlobalVariables.end(), std::inserter(missingIdentities, missingIdentities.begin())); storm::dd::Add identityEncoding = variables.manager->template getAddOne(); for (auto const& variable : missingIdentities) { identityEncoding *= variables.variableToIdentityMap.at(variable); } return identityEncoding; } EdgeDd extendEdgeWithIdentity(EdgeDd const& edge, storm::dd::Add const& identity) { EdgeDd result(edge); result.transitions *= identity; return result; } EdgeDd composeEdgesInParallel(EdgeDd const& edge1, EdgeDd const& edge2) { EdgeDd result; // Compose the guards. result.guard = edge1.guard * edge2.guard; // If the composed guard is already zero, we can immediately return an empty result. if (result.guard.isZero()) { result.transitions = edge1.transitions.getDdManager().template getAddZero(); return result; } // Compute the set of variables written multiple times by the composition. std::set oldVariablesWrittenMultipleTimes; std::set_union(edge1.globalVariablesWrittenMultipleTimes.begin(), edge1.globalVariablesWrittenMultipleTimes.end(), edge2.globalVariablesWrittenMultipleTimes.begin(), edge2.globalVariablesWrittenMultipleTimes.end(), std::inserter(oldVariablesWrittenMultipleTimes, oldVariablesWrittenMultipleTimes.begin())); std::set newVariablesWrittenMultipleTimes; std::set_intersection(edge1.writtenGlobalVariables.begin(), edge1.writtenGlobalVariables.end(), edge2.writtenGlobalVariables.begin(), edge2.writtenGlobalVariables.end(), std::inserter(newVariablesWrittenMultipleTimes, newVariablesWrittenMultipleTimes.begin())); std::set variablesWrittenMultipleTimes; std::set_union(oldVariablesWrittenMultipleTimes.begin(), oldVariablesWrittenMultipleTimes.end(), newVariablesWrittenMultipleTimes.begin(), newVariablesWrittenMultipleTimes.end(), std::inserter(variablesWrittenMultipleTimes, variablesWrittenMultipleTimes.begin())); result.globalVariablesWrittenMultipleTimes = std::move(variablesWrittenMultipleTimes); // Compute the set of variables written by the composition. std::set variablesWritten; std::set_union(edge1.writtenGlobalVariables.begin(), edge1.writtenGlobalVariables.end(), edge2.writtenGlobalVariables.begin(), edge2.writtenGlobalVariables.end(), std::inserter(variablesWritten, variablesWritten.begin())); result.writtenGlobalVariables = variablesWritten; // Compose the guards. result.guard = edge1.guard * edge2.guard; // Compose the transitions. result.transitions = edge1.transitions * edge2.transitions; return result; } /*! * Builds the DD for the given edge. */ EdgeDd buildEdgeDd(storm::jani::Automaton const& automaton, storm::jani::Edge const& edge) { STORM_LOG_TRACE("Translating guard " << edge.getGuard()); storm::dd::Add guard = this->variables.rowExpressionAdapter->translateExpression(edge.getGuard());// * this->variables.automatonToRangeMap.at(automaton.getName()); STORM_LOG_WARN_COND(!guard.isZero(), "The guard '" << edge.getGuard() << "' is unsatisfiable."); if (!guard.isZero()) { // Create the DDs representing the individual updates. std::vector> destinationDds; for (storm::jani::EdgeDestination const& destination : edge.getDestinations()) { destinationDds.push_back(buildEdgeDestinationDd(automaton, destination, guard, this->variables)); STORM_LOG_WARN_COND(!destinationDds.back().transitions.isZero(), "Destination does not have any effect."); } // Start by gathering all variables that were written in at least one update. std::set globalVariablesInSomeUpdate; // If the edge is not labeled with the silent action, we have to analyze which portion of the global // variables was written by any of the updates and make all update results equal w.r.t. this set. If // the edge is labeled with the silent action, we can already multiply the identities of all global variables. if (edge.getActionId() != this->model.getSilentActionIndex()) { for (auto const& edgeDestinationDd : destinationDds) { globalVariablesInSomeUpdate.insert(edgeDestinationDd.writtenGlobalVariables.begin(), edgeDestinationDd.writtenGlobalVariables.end()); } } else { globalVariablesInSomeUpdate = this->variables.allGlobalVariables; } // Then, multiply the missing identities. for (auto& destinationDd : destinationDds) { std::set missingIdentities; std::set_difference(globalVariablesInSomeUpdate.begin(), globalVariablesInSomeUpdate.end(), destinationDd.writtenGlobalVariables.begin(), destinationDd.writtenGlobalVariables.end(), std::inserter(missingIdentities, missingIdentities.begin())); for (auto const& variable : missingIdentities) { STORM_LOG_TRACE("Multiplying identity for variable " << variable.getName() << " to destination DD."); destinationDd.transitions *= this->variables.variableToIdentityMap.at(variable); } } // Now combine the destination DDs to the edge DD. storm::dd::Add transitions = this->variables.manager->template getAddZero(); for (auto const& destinationDd : destinationDds) { transitions += destinationDd.transitions; } // Add the source location and the guard. transitions *= this->variables.manager->getEncoding(this->variables.automatonToLocationVariableMap.at(automaton.getName()).first, edge.getSourceLocationId()).template toAdd() * guard; // If we multiply the ranges of global variables, make sure everything stays within its bounds. if (!globalVariablesInSomeUpdate.empty()) { transitions *= this->variables.globalVariableRanges; } // If the edge has a rate, we multiply it to the DD. if (edge.hasRate()) { transitions *= this->variables.rowExpressionAdapter->translateExpression(edge.getRate()); } return EdgeDd(guard, transitions, globalVariablesInSomeUpdate); } else { return EdgeDd(this->variables.manager->template getAddZero(), this->variables.manager->template getAddZero()); } } /*! * Builds the DD for the automaton with the given name. */ AutomatonDd buildAutomatonDd(std::string const& automatonName) { AutomatonDd result(this->variables.automatonToIdentityMap.at(automatonName)); storm::jani::Automaton const& automaton = this->model.getAutomaton(automatonName); for (auto const& edge : automaton.getEdges()) { // Build the edge and add it if it adds transitions. EdgeDd edgeDd = buildEdgeDd(automaton, edge); if (!edgeDd.guard.isZero()) { result.actionIndexToEdges[edge.getActionId()].push_back(edgeDd); } } return result; } }; template class CombinedEdgesSystemComposer : public SystemComposer { public: // This structure represents an edge. struct EdgeDd { EdgeDd(storm::dd::Add const& guard = storm::dd::Add(), storm::dd::Add const& transitions = storm::dd::Add(), std::set const& writtenGlobalVariables = {}) : guard(guard), transitions(transitions), writtenGlobalVariables(writtenGlobalVariables) { // Intentionally left empty. } // A DD that represents all states that have this edge enabled. storm::dd::Add guard; // A DD that represents the transitions of this edge. storm::dd::Add transitions; // The set of global variables written by this edge. std::set writtenGlobalVariables; }; // This structure represents an edge. struct ActionDd { ActionDd(storm::dd::Add const& guard = storm::dd::Add(), storm::dd::Add const& transitions = storm::dd::Add(), std::pair localNondeterminismVariables = std::pair(0, 0), std::map> const& variableToWritingFragment = {}, storm::dd::Bdd const& illegalFragment = storm::dd::Bdd()) : guard(guard), transitions(transitions), localNondeterminismVariables(localNondeterminismVariables), variableToWritingFragment(variableToWritingFragment), illegalFragment(illegalFragment) { // Intentionally left empty. } uint64_t getLowestLocalNondeterminismVariable() const { return localNondeterminismVariables.first; } uint64_t getHighestLocalNondeterminismVariable() const { return localNondeterminismVariables.second; } std::pair const& getLocalNondeterminismVariables() const { return localNondeterminismVariables; } // A DD that represents all states that have this edge enabled. storm::dd::Add guard; // A DD that represents the transitions of this edge. storm::dd::Add transitions; // The local nondeterminism variables used by this action DD, given as the lowest std::pair localNondeterminismVariables; // A mapping from global variables to a DD that characterizes choices (nondeterminism variables) in // states that write to this global variable. std::map> variableToWritingFragment; // A DD characterizing the fragment of the states satisfying the guard that are illegal because // there are synchronizing edges enabled that write to the same global variable. storm::dd::Bdd illegalFragment; }; // This structure represents a subcomponent of a composition. struct AutomatonDd { AutomatonDd(storm::dd::Add const& identity) : actionIndexToAction(), identity(identity), localNondeterminismVariables(std::make_pair(0, 0)) { // Intentionally left empty. } uint64_t getLowestLocalNondeterminismVariable() const { return localNondeterminismVariables.first; } void setLowestLocalNondeterminismVariable(uint64_t newValue) { localNondeterminismVariables.first = newValue; } uint64_t getHighestLocalNondeterminismVariable() const { return localNondeterminismVariables.second; } void setHighestLocalNondeterminismVariable(uint64_t newValue) { localNondeterminismVariables.second = newValue; } void extendLocalNondeterminismVariables(std::pair const& localNondeterminismVariables) { setLowestLocalNondeterminismVariable(std::min(localNondeterminismVariables.first, getLowestLocalNondeterminismVariable())); setHighestLocalNondeterminismVariable(std::max(localNondeterminismVariables.second, getHighestLocalNondeterminismVariable())); } // A mapping from action indices to the action DDs. std::map actionIndexToAction; // The identity of the automaton's variables. storm::dd::Add identity; // The local nondeterminism variables used by this action DD, given as the lowest std::pair localNondeterminismVariables; }; CombinedEdgesSystemComposer(storm::jani::Model const& model, CompositionVariables const& variables) : SystemComposer(model, variables) { // Intentionally left empty. } ComposerResult compose() override { std::map actionIndexToLocalNondeterminismVariableOffset; for (auto const& action : this->model.getActions()) { actionIndexToLocalNondeterminismVariableOffset[this->model.getActionIndex(action.getName())] = 0; } AutomatonDd globalAutomaton = boost::any_cast(this->model.getSystemComposition().accept(*this, actionIndexToLocalNondeterminismVariableOffset)); return buildSystemFromAutomaton(globalAutomaton); } boost::any visit(storm::jani::AutomatonComposition const& composition, boost::any const& data) override { std::map const& actionIndexToLocalNondeterminismVariableOffset = boost::any_cast const&>(data); return buildAutomatonDd(composition.getAutomatonName(), actionIndexToLocalNondeterminismVariableOffset); } boost::any visit(storm::jani::RenameComposition const& composition, boost::any const& data) override { // Build a mapping from indices to indices for the renaming. std::map renamingIndexToIndex; for (auto const& entry : composition.getRenaming()) { if (this->model.getActionIndex(entry.first) != this->model.getSilentActionIndex()) { // Distinguish the cases where we hide the action or properly rename it. if (entry.second) { renamingIndexToIndex.emplace(this->model.getActionIndex(entry.first), this->model.getActionIndex(entry.second.get())); } else { renamingIndexToIndex.emplace(this->model.getActionIndex(entry.first), this->model.getSilentActionIndex()); } } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Renaming composition must not rename the silent action."); } } // Prepare the new offset mapping. std::map const& actionIndexToLocalNondeterminismVariableOffset = boost::any_cast const&>(data); std::map newSynchronizingActionToOffsetMap = actionIndexToLocalNondeterminismVariableOffset; for (auto const& indexPair : renamingIndexToIndex) { auto it = actionIndexToLocalNondeterminismVariableOffset.find(indexPair.second); STORM_LOG_THROW(it != actionIndexToLocalNondeterminismVariableOffset.end(), storm::exceptions::InvalidArgumentException, "Invalid action index " << indexPair.second << "."); newSynchronizingActionToOffsetMap[indexPair.first] = it->second; } // Then, we translate the subcomposition. AutomatonDd subautomaton = boost::any_cast(composition.getSubcomposition().accept(*this, newSynchronizingActionToOffsetMap)); // Now perform the renaming and return result. return rename(subautomaton, renamingIndexToIndex); } boost::any visit(storm::jani::ParallelComposition const& composition, boost::any const& data) override { // Build the set of synchronizing action indices. std::set synchronizingActionIndices; for (auto const& entry : composition.getSynchronizationAlphabet()) { if (this->model.getActionIndex(entry) != this->model.getSilentActionIndex()) { synchronizingActionIndices.insert(this->model.getActionIndex(entry)); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Parallel composition must not synchronize over silent action."); } } // Then translate the left subcomposition. AutomatonDd left = boost::any_cast(composition.getLeftSubcomposition().accept(*this, data)); // Prepare the new offset mapping. std::map const& actionIndexToLocalNondeterminismVariableOffset = boost::any_cast const&>(data); std::map newActionIndexToLocalNondeterminismVariableOffset = actionIndexToLocalNondeterminismVariableOffset; for (auto const& actionIndex : synchronizingActionIndices) { auto it = left.actionIndexToAction.find(actionIndex); if (it != left.actionIndexToAction.end()) { newActionIndexToLocalNondeterminismVariableOffset[actionIndex] = it->second.getHighestLocalNondeterminismVariable(); } } // Then translate the right subcomposition. AutomatonDd right = boost::any_cast(composition.getRightSubcomposition().accept(*this, newActionIndexToLocalNondeterminismVariableOffset)); return composeInParallel(left, right, synchronizingActionIndices); } private: AutomatonDd composeInParallel(AutomatonDd const& automaton1, AutomatonDd const& automaton2, std::set const& synchronizingActionIndices) { AutomatonDd result(automaton1); // Treat all actions of the first automaton. for (auto const& action1 : automaton1.actionIndexToAction) { // If we need to synchronize over this action index, we try to do so now. if (synchronizingActionIndices.find(action1.first) != synchronizingActionIndices.end()) { auto action2It = automaton2.actionIndexToAction.find(action1.first); if (action2It != automaton2.actionIndexToAction.end()) { ActionDd newAction = combineSynchronizingActions(action1.second, action2It->second); result.actionIndexToAction[action1.first] = newAction; result.extendLocalNondeterminismVariables(newAction.getLocalNondeterminismVariables()); } } else { // If we don't synchronize over this action, we need to construct the interleaving. // If both automata contain the action, we need to combine the actions in an unsynchronized way. auto action2It = automaton2.actionIndexToAction.find(action1.first); if (action2It != automaton2.actionIndexToAction.end()) { ActionDd newAction = combineUnsynchronizedActions(action1.second, action2It->second, automaton1.identity, automaton2.identity); result.actionIndexToAction[action1.first] = newAction; result.extendLocalNondeterminismVariables(newAction.getLocalNondeterminismVariables()); } else { // If only the first automaton has this action, we only need to apply the identity of the // second automaton. result.actionIndexToAction[action1.first] = ActionDd(action1.second.guard, action1.second.transitions * automaton2.identity, action1.second.localNondeterminismVariables, action1.second.variableToWritingFragment, action1.second.illegalFragment); } } } // Treat the actions of the second automaton. for (auto const& action2 : automaton2.actionIndexToAction) { // Here, we only need to treat actions that the first automaton does not have, because we have handled // this case earlier. Likewise, we have to consider non-synchronizing actions only. if (automaton1.actionIndexToAction.find(action2.first) == automaton1.actionIndexToAction.end()) { if (synchronizingActionIndices.find(action2.first) == synchronizingActionIndices.end()) { // If only the second automaton has this action, we only need to apply the identity of the // first automaton. result.actionIndexToAction[action2.first] = ActionDd(action2.second.guard, action2.second.transitions * automaton1.identity, action2.second.localNondeterminismVariables, action2.second.variableToWritingFragment, action2.second.illegalFragment); } } } result.identity = automaton1.identity * automaton2.identity; return result; } ActionDd combineSynchronizingActions(ActionDd action1, ActionDd action2) { storm::dd::Bdd illegalFragment = this->variables.manager->getBddZero(); storm::dd::Bdd guard1 = action1.guard.toBdd(); storm::dd::Bdd guard2 = action2.guard.toBdd(); storm::dd::Bdd combinedGuard = guard1 && guard2; // Cross-multiply the guards to the other fragments that write global variables and check for overlapping // parts. This finds illegal parts in which a global variable is written multiple times. std::map> globalVariableToWritingFragment; for (auto& entry : action1.variableToWritingFragment) { entry.second &= guard2; globalVariableToWritingFragment[entry.first] = entry.second; } for (auto& entry : action2.variableToWritingFragment) { entry.second &= guard1; auto it = globalVariableToWritingFragment.find(entry.first); if (it != globalVariableToWritingFragment.end()) { auto action1LocalNondeterminismVariableSet = std::set(this->variables.localNondeterminismVariables.begin() + action1.getLowestLocalNondeterminismVariable(), this->variables.localNondeterminismVariables.begin() + action1.getHighestLocalNondeterminismVariable()); auto action2LocalNondeterminismVariableSet = std::set(this->variables.localNondeterminismVariables.begin() + action2.getLowestLocalNondeterminismVariable(), this->variables.localNondeterminismVariables.begin() + action2.getHighestLocalNondeterminismVariable()); illegalFragment |= it->second.existsAbstract(action1LocalNondeterminismVariableSet) && entry.second.existsAbstract(action2LocalNondeterminismVariableSet); it->second &= entry.second; } else { globalVariableToWritingFragment[entry.first] = entry.second; } } return ActionDd(action1.guard * action2.guard, action1.transitions * action2.transitions, std::make_pair(std::min(action1.getLowestLocalNondeterminismVariable(), action2.getLowestLocalNondeterminismVariable()), std::max(action1.getHighestLocalNondeterminismVariable(), action2.getHighestLocalNondeterminismVariable())), globalVariableToWritingFragment, illegalFragment); } ActionDd combineUnsynchronizedActions(ActionDd action1, ActionDd action2, storm::dd::Add const& identity1, storm::dd::Add const& identity2) { // First extend the action DDs by the other identities. STORM_LOG_TRACE("Multiplying identities to combine unsynchronized actions."); action1.transitions = action1.transitions * identity2; action2.transitions = action2.transitions * identity1; // Then combine the extended action DDs. return combineUnsynchronizedActions(action1, action2); } ActionDd combineUnsynchronizedActions(ActionDd action1, ActionDd action2) { STORM_LOG_TRACE("Combining unsynchronized actions."); if (this->model.getModelType() == storm::jani::ModelType::DTMC || this->model.getModelType() == storm::jani::ModelType::CTMC) { return ActionDd(action1.guard + action2.guard, action1.transitions + action2.transitions, std::make_pair(0, 0), joinVariableWritingFragmentMaps(action1.variableToWritingFragment, action2.variableToWritingFragment), this->variables.manager->getBddZero()); } else if (this->model.getModelType() == storm::jani::ModelType::MDP) { if (action1.transitions.isZero()) { return action2; } else if (action2.transitions.isZero()) { return action1; } // Bring both choices to the same number of variables that encode the nondeterminism. assert(action1.getLowestLocalNondeterminismVariable() == action2.getLowestLocalNondeterminismVariable()); uint_fast64_t highestLocalNondeterminismVariable = std::max(action1.getHighestLocalNondeterminismVariable(), action2.getHighestLocalNondeterminismVariable()); if (action1.getHighestLocalNondeterminismVariable() > action2.getHighestLocalNondeterminismVariable()) { storm::dd::Add nondeterminismEncoding = this->variables.manager->template getAddOne(); for (uint_fast64_t i = action2.getHighestLocalNondeterminismVariable(); i < action1.getHighestLocalNondeterminismVariable(); ++i) { nondeterminismEncoding *= this->variables.manager->getEncoding(this->variables.localNondeterminismVariables[i], 0).template toAdd(); } action2.transitions *= nondeterminismEncoding; } else if (action2.getHighestLocalNondeterminismVariable() > action1.getHighestLocalNondeterminismVariable()) { storm::dd::Add nondeterminismEncoding = this->variables.manager->template getAddOne(); for (uint_fast64_t i = action1.getHighestLocalNondeterminismVariable(); i < action2.getHighestLocalNondeterminismVariable(); ++i) { nondeterminismEncoding *= this->variables.manager->getEncoding(this->variables.localNondeterminismVariables[i], 0).template toAdd(); } action1.transitions *= nondeterminismEncoding; } // Add a new variable that resolves the nondeterminism between the two choices. storm::dd::Add combinedTransitions = this->variables.manager->getEncoding(this->variables.localNondeterminismVariables[highestLocalNondeterminismVariable], 1).ite(action2.transitions, action1.transitions); // Add the new variable to the writing fragments of each global variable before joining them. for (auto& entry : action1.variableToWritingFragment) { entry.second = this->variables.manager->getEncoding(this->variables.localNondeterminismVariables[highestLocalNondeterminismVariable], 0) && entry.second; } for (auto& entry : action2.variableToWritingFragment) { entry.second = this->variables.manager->getEncoding(this->variables.localNondeterminismVariables[highestLocalNondeterminismVariable], 1) && entry.second; } return ActionDd((action1.guard.toBdd() || action2.guard.toBdd()).template toAdd(), combinedTransitions, std::make_pair(action1.getLowestLocalNondeterminismVariable(), highestLocalNondeterminismVariable + 1), joinVariableWritingFragmentMaps(action1.variableToWritingFragment, action2.variableToWritingFragment), action1.illegalFragment || action2.illegalFragment); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Illegal model type."); } } AutomatonDd rename(AutomatonDd const& automaton, std::map const& indexToIndex) { AutomatonDd result(automaton.identity); for (auto const& action : automaton.actionIndexToAction) { auto renamingIt = indexToIndex.find(action.first); if (renamingIt != indexToIndex.end()) { // If the action is to be renamed and an action with the target index already exists, we need // to combine the action DDs. auto itNewActions = result.actionIndexToAction.find(renamingIt->second); if (itNewActions != result.actionIndexToAction.end()) { itNewActions->second = combineUnsynchronizedActions(action.second, itNewActions->second); } else { // In this case, we can simply copy the action over. result.actionIndexToAction[renamingIt->second] = action.second; } } else { // If the action is not to be renamed, we need to copy it over. However, if some other action // was renamed to the very same action name before, we need to combine the transitions. auto itNewActions = result.actionIndexToAction.find(action.first); if (itNewActions != result.actionIndexToAction.end()) { itNewActions->second = combineUnsynchronizedActions(action.second, itNewActions->second); } else { // In this case, we can simply copy the action over. result.actionIndexToAction[action.first] = action.second; } } } return result; } EdgeDd buildEdgeDd(storm::jani::Automaton const& automaton, storm::jani::Edge const& edge) { STORM_LOG_TRACE("Translating guard " << edge.getGuard()); // We keep the guard and a "ranged" version seperate, because building the destinations tends to be // slower when the full range is applied. storm::dd::Add guard = this->variables.rowExpressionAdapter->translateExpression(edge.getGuard()); storm::dd::Add rangedGuard = guard * this->variables.automatonToRangeMap.at(automaton.getName()); STORM_LOG_WARN_COND(!rangedGuard.isZero(), "The guard '" << edge.getGuard() << "' is unsatisfiable."); if (!rangedGuard.isZero()) { // Create the DDs representing the individual updates. std::vector> destinationDds; for (storm::jani::EdgeDestination const& destination : edge.getDestinations()) { destinationDds.push_back(buildEdgeDestinationDd(automaton, destination, guard, this->variables)); STORM_LOG_WARN_COND(!destinationDds.back().transitions.isZero(), "Destination does not have any effect."); } // Now that we have built the destinations, we always take the full guard. guard = rangedGuard; // Start by gathering all variables that were written in at least one destination. std::set globalVariablesInSomeDestination; // If the edge is not labeled with the silent action, we have to analyze which portion of the global // variables was written by any of the updates and make all update results equal w.r.t. this set. If // the edge is labeled with the silent action, we can already multiply the identities of all global variables. if (edge.getActionId() != this->model.getSilentActionIndex()) { for (auto const& edgeDestinationDd : destinationDds) { globalVariablesInSomeDestination.insert(edgeDestinationDd.writtenGlobalVariables.begin(), edgeDestinationDd.writtenGlobalVariables.end()); } } else { globalVariablesInSomeDestination = this->variables.allGlobalVariables; } // Then, multiply the missing identities. for (auto& destinationDd : destinationDds) { std::set missingIdentities; std::set_difference(globalVariablesInSomeDestination.begin(), globalVariablesInSomeDestination.end(), destinationDd.writtenGlobalVariables.begin(), destinationDd.writtenGlobalVariables.end(), std::inserter(missingIdentities, missingIdentities.begin())); for (auto const& variable : missingIdentities) { STORM_LOG_TRACE("Multiplying identity for variable " << variable.getName() << " to destination DD."); destinationDd.transitions *= this->variables.variableToIdentityMap.at(variable); } } // Now combine the destination DDs to the edge DD. storm::dd::Add transitions = this->variables.manager->template getAddZero(); for (auto const& destinationDd : destinationDds) { transitions += destinationDd.transitions; } // Add the source location and the guard. transitions *= this->variables.manager->getEncoding(this->variables.automatonToLocationVariableMap.at(automaton.getName()).first, edge.getSourceLocationId()).template toAdd() * guard; // If we multiply the ranges of global variables, make sure everything stays within its bounds. if (!globalVariablesInSomeDestination.empty()) { transitions *= this->variables.globalVariableRanges; } // If the edge has a rate, we multiply it to the DD. if (edge.hasRate()) { transitions *= this->variables.rowExpressionAdapter->translateExpression(edge.getRate()); } return EdgeDd(guard, guard * transitions, globalVariablesInSomeDestination); } else { return EdgeDd(this->variables.manager->template getAddZero(), this->variables.manager->template getAddZero()); } } ActionDd buildActionDdForActionIndex(storm::jani::Automaton const& automaton, uint64_t actionIndex, uint64_t localNondeterminismVariableOffset) { // Translate the individual edges. std::vector edgeDds; for (auto const& edge : automaton.getEdges()) { if (edge.getActionId() == actionIndex) { edgeDds.push_back(buildEdgeDd(automaton, edge)); } } // Now combine the edges to a single action. if (!edgeDds.empty()) { switch (this->model.getModelType()){ case storm::jani::ModelType::DTMC: case storm::jani::ModelType::CTMC: return combineEdgesToActionMarkovChain(edgeDds); break; case storm::jani::ModelType::MDP: return combineEdgesToActionMdp(edgeDds, localNondeterminismVariableOffset); break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Cannot translate model of this type."); } } else { return ActionDd(this->variables.manager->template getAddZero(), this->variables.manager->template getAddZero(), std::make_pair(0, 0), {}, this->variables.manager->getBddZero()); } } ActionDd combineEdgesToActionMarkovChain(std::vector const& edgeDds) { storm::dd::Bdd allGuards = this->variables.manager->getBddZero(); storm::dd::Add allTransitions = this->variables.manager->template getAddZero(); storm::dd::Bdd temporary; std::map> globalVariableToWritingFragment; for (auto const& edgeDd : edgeDds) { // Check for overlapping guards. storm::dd::Bdd guardBdd = edgeDd.guard.toBdd(); temporary = guardBdd && allGuards; // Issue a warning if there are overlapping guards in a DTMC. STORM_LOG_WARN_COND(temporary.isZero() || this->model.getModelType() == storm::jani::ModelType::CTMC, "Guard of a command overlaps with previous guards."); // Add the elements of the current edge to the global ones. allGuards |= guardBdd; allTransitions += edgeDd.transitions; // Keep track of the fragment that is writing global variables. for (auto const& variable : edgeDd.writtenGlobalVariables) { auto it = globalVariableToWritingFragment.find(variable); if (it != globalVariableToWritingFragment.end()) { it->second |= guardBdd; } else { globalVariableToWritingFragment[variable] = guardBdd; } } } return ActionDd(allGuards.template toAdd(), allTransitions, std::make_pair(0, 0), globalVariableToWritingFragment, this->variables.manager->getBddZero()); } void addToVariableWritingFragmentMap(std::map>& globalVariableToWritingFragment, storm::expressions::Variable const& variable, storm::dd::Bdd const& partToAdd) const { auto it = globalVariableToWritingFragment.find(variable); if (it != globalVariableToWritingFragment.end()) { it->second |= partToAdd; } else { globalVariableToWritingFragment.emplace(variable, partToAdd); } } std::map> joinVariableWritingFragmentMaps(std::map> const& globalVariableToWritingFragment1, std::map> const& globalVariableToWritingFragment2) { std::map> result = globalVariableToWritingFragment1; for (auto const& entry : globalVariableToWritingFragment2) { auto resultIt = result.find(entry.first); if (resultIt != result.end()) { resultIt->second |= entry.second; } else { result[entry.first] = entry.second; } } return result; } ActionDd combineEdgesBySummation(storm::dd::Add const& guard, std::vector const& edges) { storm::dd::Add transitions = this->variables.manager->template getAddZero(); std::map> globalVariableToWritingFragment; for (auto const& edge : edges) { transitions += edge.transitions; for (auto const& variable : edge.writtenGlobalVariables) { addToVariableWritingFragmentMap(globalVariableToWritingFragment, variable, edge.guard.toBdd()); } } return ActionDd(guard, transitions, std::make_pair(0, 0), globalVariableToWritingFragment, this->variables.manager->getBddZero()); } ActionDd combineEdgesToActionMdp(std::vector const& edges, uint64_t localNondeterminismVariableOffset) { // Sum all guards, so we can read off the maximal number of nondeterministic choices in any given state. storm::dd::Bdd allGuards = this->variables.manager->getBddZero(); storm::dd::Add sumOfGuards = this->variables.manager->template getAddZero(); for (auto const& edge : edges) { sumOfGuards += edge.guard; allGuards |= edge.guard.toBdd(); } uint_fast64_t maxChoices = static_cast(sumOfGuards.getMax()); STORM_LOG_TRACE("Found " << maxChoices << " local choices."); // Depending on the maximal number of nondeterminstic choices, we need to use some variables to encode the nondeterminism. if (maxChoices <= 1) { return combineEdgesBySummation(allGuards.template toAdd(), edges); } else { // Calculate number of required variables to encode the nondeterminism. uint_fast64_t numberOfBinaryVariables = static_cast(std::ceil(storm::utility::math::log2(maxChoices))); storm::dd::Add allEdges = this->variables.manager->template getAddZero(); std::map> globalVariableToWritingFragment; storm::dd::Bdd equalsNumberOfChoicesDd; std::vector> choiceDds(maxChoices, this->variables.manager->template getAddZero()); std::vector> remainingDds(maxChoices, this->variables.manager->getBddZero()); std::vector, storm::dd::Add>> indicesEncodedWithLocalNondeterminismVariables; for (uint64_t j = 0; j < maxChoices; ++j) { storm::dd::Add indexEncoding = encodeIndex(j, localNondeterminismVariableOffset, numberOfBinaryVariables, this->variables); indicesEncodedWithLocalNondeterminismVariables.push_back(std::make_pair(indexEncoding.toBdd(), indexEncoding)); } for (uint_fast64_t currentChoices = 1; currentChoices <= maxChoices; ++currentChoices) { // Determine the set of states with exactly currentChoices choices. equalsNumberOfChoicesDd = sumOfGuards.equals(this->variables.manager->getConstant(static_cast(currentChoices))); // If there is no such state, continue with the next possible number of choices. if (equalsNumberOfChoicesDd.isZero()) { continue; } // Reset the previously used intermediate storage. for (uint_fast64_t j = 0; j < currentChoices; ++j) { choiceDds[j] = this->variables.manager->template getAddZero(); remainingDds[j] = equalsNumberOfChoicesDd; } for (std::size_t j = 0; j < edges.size(); ++j) { EdgeDd const& currentEdge = edges[j]; // Check if edge guard overlaps with equalsNumberOfChoicesDd. That is, there are states with exactly currentChoices // choices such that one outgoing choice is given by the j-th edge. storm::dd::Bdd guardChoicesIntersection = currentEdge.guard.toBdd() && equalsNumberOfChoicesDd; // If there is no such state, continue with the next command. if (guardChoicesIntersection.isZero()) { continue; } // Split the currentChoices nondeterministic choices. for (uint_fast64_t k = 0; k < currentChoices; ++k) { // Calculate the overlapping part of command guard and the remaining DD. storm::dd::Bdd remainingGuardChoicesIntersection = guardChoicesIntersection && remainingDds[k]; // Check if we can add some overlapping parts to the current index. if (!remainingGuardChoicesIntersection.isZero()) { // Remove overlapping parts from the remaining DD. remainingDds[k] = remainingDds[k] && !remainingGuardChoicesIntersection; // Combine the overlapping part of the guard with command updates and add it to the resulting DD. choiceDds[k] += remainingGuardChoicesIntersection.template toAdd() * currentEdge.transitions; // Keep track of the written global variables of the fragment. for (auto const& variable : currentEdge.writtenGlobalVariables) { addToVariableWritingFragmentMap(globalVariableToWritingFragment, variable, remainingGuardChoicesIntersection && indicesEncodedWithLocalNondeterminismVariables[k].first); } } // Remove overlapping parts from the command guard DD guardChoicesIntersection = guardChoicesIntersection && !remainingGuardChoicesIntersection; // If the guard DD has become equivalent to false, we can stop here. if (guardChoicesIntersection.isZero()) { break; } } } // Add the meta variables that encode the nondeterminisim to the different choices. for (uint_fast64_t j = 0; j < currentChoices; ++j) { allEdges += indicesEncodedWithLocalNondeterminismVariables[j].second * choiceDds[j]; } // Delete currentChoices out of overlapping DD sumOfGuards = sumOfGuards * (!equalsNumberOfChoicesDd).template toAdd(); } return ActionDd(allGuards.template toAdd(), allEdges, std::make_pair(localNondeterminismVariableOffset, localNondeterminismVariableOffset + numberOfBinaryVariables), globalVariableToWritingFragment, this->variables.manager->getBddZero()); } } AutomatonDd buildAutomatonDd(std::string const& automatonName, std::map const& actionIndexToLocalNondeterminismVariableOffset) { AutomatonDd result(this->variables.automatonToIdentityMap.at(automatonName)); storm::jani::Automaton const& automaton = this->model.getAutomaton(automatonName); for (auto const& action : this->model.getActions()) { uint64_t actionIndex = this->model.getActionIndex(action.getName()); if (!automaton.hasEdgeLabeledWithActionIndex(actionIndex)) { continue; } ActionDd actionDd = buildActionDdForActionIndex(automaton, actionIndex, actionIndexToLocalNondeterminismVariableOffset.at(actionIndex)); result.actionIndexToAction[actionIndex] = actionDd; result.setLowestLocalNondeterminismVariable(std::max(result.getLowestLocalNondeterminismVariable(), actionDd.getLowestLocalNondeterminismVariable())); result.setHighestLocalNondeterminismVariable(std::max(result.getHighestLocalNondeterminismVariable(), actionDd.getHighestLocalNondeterminismVariable())); } return result; } void addMissingGlobalVariableIdentities(ActionDd& action) { // Build a DD that we can multiply to the transitions and adds all missing global variable identities that way. storm::dd::Add missingIdentities = this->variables.manager->template getAddOne(); for (auto const& variable : this->variables.allGlobalVariables) { auto it = action.variableToWritingFragment.find(variable); if (it != action.variableToWritingFragment.end()) { missingIdentities *= (it->second).ite(this->variables.manager->template getAddOne(), this->variables.variableToIdentityMap.at(variable)); } else { missingIdentities *= this->variables.variableToIdentityMap.at(variable); } } action.transitions *= missingIdentities; } ComposerResult buildSystemFromAutomaton(AutomatonDd& automaton) { // If the model is an MDP, we need to encode the nondeterminism using additional variables. if (this->model.getModelType() == storm::jani::ModelType::MDP) { storm::dd::Add result = this->variables.manager->template getAddZero(); storm::dd::Bdd illegalFragment = this->variables.manager->getBddZero(); // First, determine the highest number of nondeterminism variables that is used in any action and make // all actions use the same amout of nondeterminism variables. uint64_t numberOfUsedNondeterminismVariables = automaton.getHighestLocalNondeterminismVariable(); // Add missing global variable identities, action and nondeterminism encodings. for (auto& action : automaton.actionIndexToAction) { illegalFragment |= action.second.illegalFragment; addMissingGlobalVariableIdentities(action.second); storm::dd::Add actionEncoding = encodeAction(action.first != this->model.getSilentActionIndex() ? boost::optional(action.first) : boost::none, this->variables); storm::dd::Add missingNondeterminismEncoding = encodeIndex(0, action.second.getHighestLocalNondeterminismVariable(), numberOfUsedNondeterminismVariables - action.second.getHighestLocalNondeterminismVariable(), this->variables); storm::dd::Add extendedTransitions = actionEncoding * missingNondeterminismEncoding * action.second.transitions; result += extendedTransitions; } return ComposerResult(result, illegalFragment, numberOfUsedNondeterminismVariables); } else if (this->model.getModelType() == storm::jani::ModelType::DTMC || this->model.getModelType() == storm::jani::ModelType::CTMC) { // Simply add all actions, but make sure to include the missing global variable identities. storm::dd::Add result = this->variables.manager->template getAddZero(); storm::dd::Bdd illegalFragment = this->variables.manager->getBddZero(); for (auto& action : automaton.actionIndexToAction) { illegalFragment |= action.second.illegalFragment; addMissingGlobalVariableIdentities(action.second); result += action.second.transitions; } return ComposerResult(result, illegalFragment, 0); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Illegal model type."); } } }; template struct ModelComponents { storm::dd::Bdd reachableStates; storm::dd::Bdd initialStates; storm::dd::Add transitionMatrix; std::unordered_map> rewardModels; }; template std::shared_ptr> createModel(storm::jani::ModelType const& modelType, CompositionVariables const& variables, ModelComponents const& modelComponents) { if (modelType == storm::jani::ModelType::DTMC) { return std::shared_ptr>(new storm::models::symbolic::Dtmc(variables.manager, modelComponents.reachableStates, modelComponents.initialStates, modelComponents.transitionMatrix, variables.rowMetaVariables, variables.rowExpressionAdapter, variables.columnMetaVariables, variables.columnExpressionAdapter, variables.rowColumnMetaVariablePairs, std::map(), modelComponents.rewardModels)); } else if (modelType == storm::jani::ModelType::CTMC) { return std::shared_ptr>(new storm::models::symbolic::Ctmc(variables.manager, modelComponents.reachableStates, modelComponents.initialStates, modelComponents.transitionMatrix, variables.rowMetaVariables, variables.rowExpressionAdapter, variables.columnMetaVariables, variables.columnExpressionAdapter, variables.rowColumnMetaVariablePairs, std::map(), modelComponents.rewardModels)); } else if (modelType == storm::jani::ModelType::MDP) { return std::shared_ptr>(new storm::models::symbolic::Mdp(variables.manager, modelComponents.reachableStates, modelComponents.initialStates, modelComponents.transitionMatrix, variables.rowMetaVariables, variables.rowExpressionAdapter, variables.columnMetaVariables, variables.columnExpressionAdapter, variables.rowColumnMetaVariablePairs, variables.allNondeterminismVariables, std::map(), modelComponents.rewardModels)); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Invalid model type."); } } template void postprocessVariables(storm::jani::ModelType const& modelType, ComposerResult& system, CompositionVariables& variables) { // Add all action/row/column variables to the DD. If we omitted multiplying edges in the construction, this will // introduce the variables so they can later be abstracted without raising an error. system.transitions.addMetaVariables(variables.rowMetaVariables); system.transitions.addMetaVariables(variables.columnMetaVariables); // If the model is an MDP, we also add all action variables. if (modelType == storm::jani::ModelType::MDP) { for (auto const& actionVariablePair : variables.actionVariablesMap) { system.transitions.addMetaVariable(actionVariablePair.second); } } // Get rid of the local nondeterminism variables that were not used. for (uint64_t index = system.numberOfNondeterminismVariables; index < variables.localNondeterminismVariables.size(); ++index) { variables.allNondeterminismVariables.erase(variables.localNondeterminismVariables[index]); } variables.localNondeterminismVariables.resize(system.numberOfNondeterminismVariables); } template void postprocessSystem(storm::jani::Model const& model, ComposerResult& system, CompositionVariables const& variables, typename DdJaniModelBuilder::Options const& options) { // For DTMCs, we normalize each row to 1 (to account for non-determinism). if (model.getModelType() == storm::jani::ModelType::DTMC) { system.transitions = system.transitions / system.transitions.sumAbstract(variables.columnMetaVariables); } // If we were asked to treat some states as terminal states, we cut away their transitions now. if (options.terminalStates || options.negatedTerminalStates) { std::map constantsSubstitution = model.getConstantsSubstitution(); storm::dd::Bdd terminalStatesBdd = variables.manager->getBddZero(); if (options.terminalStates) { storm::expressions::Expression terminalExpression = options.terminalStates.get().substitute(constantsSubstitution); STORM_LOG_TRACE("Making the states satisfying " << terminalExpression << " terminal."); terminalStatesBdd = variables.rowExpressionAdapter->translateExpression(terminalExpression).toBdd(); } if (options.negatedTerminalStates) { storm::expressions::Expression negatedTerminalExpression = options.negatedTerminalStates.get().substitute(constantsSubstitution); STORM_LOG_TRACE("Making the states *not* satisfying " << negatedTerminalExpression << " terminal."); terminalStatesBdd |= !variables.rowExpressionAdapter->translateExpression(negatedTerminalExpression).toBdd(); } system.transitions *= (!terminalStatesBdd).template toAdd(); } } template storm::dd::Bdd computeInitialStates(storm::jani::Model const& model, CompositionVariables const& variables) { storm::dd::Bdd initialStates = variables.rowExpressionAdapter->translateExpression(model.getInitialStatesExpression(true)).toBdd(); for (auto const& automaton : model.getAutomata()) { initialStates &= variables.manager->getEncoding(variables.automatonToLocationVariableMap.at(automaton.getName()).first, automaton.getInitialLocationIndex()); } for (auto const& metaVariable : variables.rowMetaVariables) { initialStates &= variables.variableToRangeMap.at(metaVariable); } return initialStates; } template void fixDeadlocks(storm::jani::ModelType const& modelType, storm::dd::Add& transitionMatrix, storm::dd::Bdd const& transitionMatrixBdd, storm::dd::Bdd const& reachableStates, CompositionVariables const& variables) { // Detect deadlocks and 1) fix them if requested 2) throw an error otherwise. storm::dd::Bdd statesWithTransition = transitionMatrixBdd.existsAbstract(variables.columnMetaVariables); storm::dd::Add deadlockStates = (reachableStates && !statesWithTransition).template toAdd(); if (!deadlockStates.isZero()) { // If we need to fix deadlocks, we do so now. if (!storm::settings::getModule().isDontFixDeadlocksSet()) { STORM_LOG_INFO("Fixing deadlocks in " << deadlockStates.getNonZeroCount() << " states. The first three of these states are: "); uint_fast64_t count = 0; for (auto it = deadlockStates.begin(), ite = deadlockStates.end(); it != ite && count < 3; ++it, ++count) { STORM_LOG_INFO((*it).first.toPrettyString(variables.rowMetaVariables) << std::endl); } // Create a global identity DD. storm::dd::Add globalIdentity = variables.manager->template getAddOne(); for (auto const& identity : variables.automatonToIdentityMap) { globalIdentity *= identity.second; } for (auto const& variable : variables.allGlobalVariables) { globalIdentity *= variables.variableToIdentityMap.at(variable); } if (modelType == storm::jani::ModelType::DTMC || modelType == storm::jani::ModelType::CTMC) { // For DTMCs, we can simply add the identity of the global module for all deadlock states. transitionMatrix += deadlockStates * globalIdentity; } else if (modelType == storm::jani::ModelType::MDP) { // For MDPs, however, we need to select an action associated with the self-loop, if we do not // want to attach a lot of self-loops to the deadlock states. storm::dd::Add action = variables.manager->template getAddOne(); for (auto const& variable : variables.actionVariablesMap) { action *= variables.manager->template getIdentity(variable.second); } for (auto const& variable : variables.localNondeterminismVariables) { action *= variables.manager->template getIdentity(variable); } transitionMatrix += deadlockStates * globalIdentity * action; } } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The model contains " << deadlockStates.getNonZeroCount() << " deadlock states. Please unset the option to not fix deadlocks, if you want to fix them automatically."); } } } template std::shared_ptr> DdJaniModelBuilder::build() { // Create all necessary variables. CompositionVariableCreator variableCreator(*this->model); CompositionVariables variables = variableCreator.create(); // Create a builder to compose and build the model. // SeparateEdgesSystemComposer composer(*this->model, variables); CombinedEdgesSystemComposer composer(*this->model, variables); ComposerResult system = composer.compose(); // Postprocess the variables in place. postprocessVariables(this->model->getModelType(), system, variables); // Postprocess the system in place. postprocessSystem(*this->model, system, variables, options); // Start creating the model components. ModelComponents modelComponents; // Build initial states. modelComponents.initialStates = computeInitialStates(*this->model, variables); // Perform reachability analysis to obtain reachable states. storm::dd::Bdd transitionMatrixBdd = system.transitions.notZero(); if (this->model->getModelType() == storm::jani::ModelType::MDP) { transitionMatrixBdd = transitionMatrixBdd.existsAbstract(variables.allNondeterminismVariables); } modelComponents.reachableStates = storm::utility::dd::computeReachableStates(modelComponents.initialStates, transitionMatrixBdd, variables.rowMetaVariables, variables.columnMetaVariables); // Check that the reachable fragment does not overlap with the illegal fragment. storm::dd::Bdd reachableIllegalFragment = modelComponents.reachableStates && system.illegalFragment; STORM_LOG_THROW(reachableIllegalFragment.isZero(), storm::exceptions::WrongFormatException, "There are reachable states in the model that have synchronizing edges enabled that write the same global variable."); // Cut transitions to reachable states. storm::dd::Add reachableStatesAdd = modelComponents.reachableStates.template toAdd(); modelComponents.transitionMatrix = system.transitions * reachableStatesAdd; // Fix deadlocks if existing. fixDeadlocks(this->model->getModelType(), modelComponents.transitionMatrix, transitionMatrixBdd, modelComponents.reachableStates, variables); // Finally, create the model. return createModel(this->model->getModelType(), variables, modelComponents); } template class DdJaniModelBuilder; template class DdJaniModelBuilder; } }