#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/utility/macros.h" #include "src/utility/jani.h" #include "src/exceptions/InvalidArgumentException.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 nondeterminismVariables; // DDs representing the identity for each variable. std::map> variableToIdentityMap; // 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; } } // 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.nondeterminismVariables.push_back(variablePair.first); } // Create global variables. storm::dd::Bdd globalVariableRanges = result.manager->getBddOne(); for (auto const& variable : this->model.getGlobalVariables().getBoundedIntegerVariables()) { createVariable(variable, result); globalVariableRanges &= result.manager->getRange(result.variableToRowMetaVariableMap->at(variable.getExpressionVariable())); } for (auto const& variable : this->model.getGlobalVariables().getBooleanVariables()) { 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(); // 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; 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(); identity &= variableIdentity.toBdd(); range &= result.manager->getRange(variablePair.first); // Then create variables for the variables of the automaton. for (auto const& variable : automaton.getVariables().getBoundedIntegerVariables()) { createVariable(variable, result); identity &= result.variableToIdentityMap.at(variable.getExpressionVariable()).toBdd(); range &= result.manager->getRange(result.variableToRowMetaVariableMap->at(variable.getExpressionVariable())); } for (auto const& variable : automaton.getVariables().getBooleanVariables()) { 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::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.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.rowColumnMetaVariablePairs.push_back(variablePair); result.allGlobalVariables.insert(variable.getExpressionVariable()); } storm::jani::Model const& model; std::set automata; }; template struct EdgeDestinationDd { EdgeDestinationDd(storm::dd::Add const& transitionsDd, std::set const& writtenGlobalVariables = {}) : transitionsDd(transitionsDd), writtenGlobalVariables(writtenGlobalVariables) { // Intentionally left empty. } storm::dd::Add transitionsDd; std::set writtenGlobalVariables; }; // This structure represents an edge. template struct EdgeDd { EdgeDd(storm::dd::Add const& guardDd = storm::dd::Add(), storm::dd::Add const& transitionsDd = storm::dd::Add(), std::set const& writtenGlobalVariables = {}, std::set const& globalVariablesWrittenMultipleTimes = {}) : guardDd(guardDd), transitionsDd(transitionsDd), writtenGlobalVariables(writtenGlobalVariables), globalVariablesWrittenMultipleTimes(globalVariablesWrittenMultipleTimes) { // Intentionally left empty. } EdgeDd(EdgeDd const& other) : guardDd(other.guardDd), transitionsDd(other.transitionsDd), writtenGlobalVariables(other.writtenGlobalVariables), globalVariablesWrittenMultipleTimes(other.globalVariablesWrittenMultipleTimes) { // Intentionally left empty. } EdgeDd& operator=(EdgeDd const& other) { if (this != &other) { globalVariablesWrittenMultipleTimes = other.globalVariablesWrittenMultipleTimes; writtenGlobalVariables = other.writtenGlobalVariables; guardDd = other.guardDd; transitionsDd = other.transitionsDd; } return *this; } storm::dd::Add guardDd; storm::dd::Add transitionsDd; std::set writtenGlobalVariables; std::set globalVariablesWrittenMultipleTimes; }; // This structure represents a subcomponent of a composition. template struct AutomatonDd { AutomatonDd(storm::dd::Add const& identity) : identity(identity) { // Intentionally left empty. } std::map>> actionIndexToEdges; storm::dd::Add identity; }; template EdgeDd extendEdgeWithIdentity(EdgeDd const& edge, storm::dd::Add const& identity) { EdgeDd result(edge); result.transitionsDd *= identity; return result; } template EdgeDd composeEdgesInParallel(EdgeDd const& edge1, EdgeDd const& edge2) { EdgeDd 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.guardDd = edge1.guardDd * edge2.guardDd; // Compose the transitions. result.transitionsDd = edge1.transitionsDd * edge2.transitionsDd; return result; } static int c = 0; // A class that is responsible for performing the actual composition. template class AutomatonComposer : public storm::jani::CompositionVisitor { public: AutomatonComposer(storm::jani::Model const& model, CompositionVariables const& variables) : model(model), variables(variables) { // Intentionally left empty. } AutomatonDd compose() { return boost::any_cast>(this->model.getSystemComposition().accept(*this, boost::none)); } 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); 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) { result.actionIndexToEdges[actionEdges.first].push_back(composeEdgesInParallel(edge1, edge2)); } } } } 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: EdgeDestinationDd buildEdgeDestinationDd(storm::jani::Automaton const& automaton, storm::jani::EdgeDestination const& destination, storm::dd::Add const& guard) { storm::dd::Add transitionsDd = variables.manager->template getAddOne(); 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 updateExpression = variables.rowExpressionAdapter->translateExpression(assignment.getAssignedExpression()); // Combine the update expression with the guard. storm::dd::Add result = updateExpression * 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.manager->getRange(primedMetaVariable).template toAdd(); // Combine the assignment DDs. transitionsDd *= 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()); transitionsDd *= 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()); transitionsDd *= variables.variableToIdentityMap.at(variable.getExpressionVariable()); } } return EdgeDestinationDd(variables.manager->getEncoding(variables.automatonToLocationVariableMap.at(automaton.getName()).second, destination.getLocationId()).template toAdd() * transitionsDd * variables.rowExpressionAdapter->translateExpression(destination.getProbability()), assignedGlobalVariables); } /*! * 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 = variables.rowExpressionAdapter->translateExpression(edge.getGuard()) * 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)); STORM_LOG_WARN_COND(!destinationDds.back().transitionsDd.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()) { std::for_each(destinationDds.begin(), destinationDds.end(), [&globalVariablesInSomeUpdate] (EdgeDestinationDd const& edgeDestinationDd) { globalVariablesInSomeUpdate.insert(edgeDestinationDd.writtenGlobalVariables.begin(), edgeDestinationDd.writtenGlobalVariables.end()); } ); } else { globalVariablesInSomeUpdate = 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.transitionsDd *= variables.variableToIdentityMap.at(variable); } } // Now combine the destination DDs to the edge DD. storm::dd::Add transitionsDd = variables.manager->template getAddZero(); for (auto const& destinationDd : destinationDds) { transitionsDd += destinationDd.transitionsDd; } // Add the source location and the guard. transitionsDd *= variables.manager->getEncoding(variables.automatonToLocationVariableMap.at(automaton.getName()).first, edge.getSourceLocationId()).template toAdd() * guard; // If we Multiply the ranges of global variables if we wrote at least one to make sure everything stays within its bounds. if (!globalVariablesInSomeUpdate.empty()) { transitionsDd *= variables.globalVariableRanges; } return EdgeDd(guard, transitionsDd, globalVariablesInSomeUpdate); } else { return EdgeDd(variables.manager->template getAddZero(), variables.manager->template getAddZero()); } } /*! * Builds the DD for the automaton with the given name. */ AutomatonDd buildAutomatonDd(std::string const& automatonName) { AutomatonDd result(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.guardDd.isZero()) { result.actionIndexToEdges[edge.getActionId()].push_back(edgeDd); } } return result; } // 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; }; template storm::dd::Add encodeAction(uint64_t trueIndex, std::vector const& actionVariables, CompositionVariables const& variables) { storm::dd::Add encoding = variables.manager->template getAddZero(); trueIndex = actionVariables.size() - (trueIndex + 1); uint64_t index = 0; for (auto it = actionVariables.rbegin(), ite = actionVariables.rend(); it != ite; ++it, ++index) { if (index == trueIndex) { encoding *= variables.manager->getEncoding(*it, 1).template toAdd(); } else { encoding *= variables.manager->getEncoding(*it, 0).template toAdd(); } } return encoding; } template 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; } template storm::dd::Add encodeIndex(uint64_t index, std::vector const& nondeterminismVariables, CompositionVariables const& variables) { storm::dd::Add result = variables.manager->template getAddZero(); STORM_LOG_TRACE("Encoding " << index << " with " << nondeterminismVariables.size() << " binary variable(s)."); std::map metaVariableNameToValueMap; for (uint_fast64_t i = 0; i < nondeterminismVariables.size(); ++i) { if (index & (1ull << (nondeterminismVariables.size() - i - 1))) { metaVariableNameToValueMap.emplace(nondeterminismVariables[i], 1); } else { metaVariableNameToValueMap.emplace(nondeterminismVariables[i], 0); } } result.setValue(metaVariableNameToValueMap, 1); return result; } template storm::dd::Add createGlobalTransitionRelation(storm::jani::Model const& model, AutomatonDd const& automatonDd, CompositionVariables const& variables) { // If the model is an MDP, we need to encode the nondeterminism using additional variables. if (model.getModelType() == storm::jani::ModelType::MDP) { // Determine how many nondeterminism variables we need. std::vector actionVariables; std::map actionIndexToVariableIndex; uint64_t maximalNumberOfEdgesPerAction = 0; for (auto const& action : automatonDd.actionIndexToEdges) { actionVariables.push_back(variables.actionVariablesMap.at(action.first)); actionIndexToVariableIndex[action.first] = actionVariables.size() - 1; maximalNumberOfEdgesPerAction = std::max(maximalNumberOfEdgesPerAction, static_cast(action.second.size())); } uint64_t numberOfNondeterminismVariables = static_cast(std::ceil(std::log2(maximalNumberOfEdgesPerAction))); std::vector nondeterminismVariables(numberOfNondeterminismVariables); std::copy(variables.nondeterminismVariables.begin(), variables.nondeterminismVariables.begin() + numberOfNondeterminismVariables, nondeterminismVariables.begin()); // Prepare result. storm::dd::Add result = variables.manager->template getAddZero(); // Add edges to the result. for (auto const& action : automatonDd.actionIndexToEdges) { storm::dd::Add edgesForAction = variables.manager->template getAddZero(); uint64_t edgeIndex = 0; for (auto const& edge : action.second) { edgesForAction += edge.transitionsDd * computeMissingGlobalVariableIdentities(edge, variables) * encodeIndex(edgeIndex, nondeterminismVariables, variables); ++edgeIndex; } result += edgesForAction * encodeAction(actionIndexToVariableIndex.at(action.first), actionVariables, variables); } } else if (model.getModelType() == storm::jani::ModelType::DTMC || model.getModelType() == storm::jani::ModelType::CTMC) { // Simply add all actions, but make sure to include the missing global variable identities. storm::dd::Add result = variables.manager->template getAddZero(); for (auto const& action : automatonDd.actionIndexToEdges) { for (auto const& edge : action.second) { result += edge.transitionsDd * computeMissingGlobalVariableIdentities(edge, variables); } } return result; } else { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Illegal model type."); } return storm::dd::Add(); } template std::shared_ptr> DdJaniModelBuilder::translate() { CompositionVariableCreator variableCreator(*this->model); CompositionVariables variables = variableCreator.create(); AutomatonComposer composer(*this->model, variables); AutomatonDd system = composer.compose(); storm::dd::Add transitions = createGlobalTransitionRelation(*this->model, system, variables); // FIXME return nullptr; } template class DdJaniModelBuilder; template class DdJaniModelBuilder; } }