您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

1884 行
132 KiB

  1. #include "storm/builder/DdJaniModelBuilder.h"
  2. #include <sstream>
  3. #include <boost/algorithm/string/join.hpp>
  4. #include "storm/logic/Formulas.h"
  5. #include "storm/storage/jani/Model.h"
  6. #include "storm/storage/jani/AutomatonComposition.h"
  7. #include "storm/storage/jani/ParallelComposition.h"
  8. #include "storm/storage/jani/CompositionInformationVisitor.h"
  9. #include "storm/storage/dd/Add.h"
  10. #include "storm/storage/dd/Bdd.h"
  11. #include "storm/adapters/AddExpressionAdapter.h"
  12. #include "storm/storage/expressions/ExpressionManager.h"
  13. #include "storm/models/symbolic/Dtmc.h"
  14. #include "storm/models/symbolic/Ctmc.h"
  15. #include "storm/models/symbolic/Mdp.h"
  16. #include "storm/models/symbolic/StandardRewardModel.h"
  17. #include "storm/settings/SettingsManager.h"
  18. #include "storm/settings/modules/CoreSettings.h"
  19. #include "storm/utility/macros.h"
  20. #include "storm/utility/jani.h"
  21. #include "storm/utility/dd.h"
  22. #include "storm/utility/math.h"
  23. #include "storm/exceptions/WrongFormatException.h"
  24. #include "storm/exceptions/InvalidSettingsException.h"
  25. #include "storm/exceptions/InvalidArgumentException.h"
  26. #include "storm/exceptions/InvalidStateException.h"
  27. #include "storm/exceptions/NotSupportedException.h"
  28. namespace storm {
  29. namespace builder {
  30. template <storm::dd::DdType Type, typename ValueType>
  31. DdJaniModelBuilder<Type, ValueType>::Options::Options(bool buildAllLabels, bool buildAllRewardModels) : buildAllLabels(buildAllLabels), buildAllRewardModels(buildAllRewardModels), rewardModelsToBuild(), constantDefinitions(), terminalStates(), negatedTerminalStates() {
  32. // Intentionally left empty.
  33. }
  34. template <storm::dd::DdType Type, typename ValueType>
  35. DdJaniModelBuilder<Type, ValueType>::Options::Options(storm::logic::Formula const& formula) : buildAllRewardModels(false), rewardModelsToBuild(), constantDefinitions(), terminalStates(), negatedTerminalStates() {
  36. this->preserveFormula(formula);
  37. this->setTerminalStatesFromFormula(formula);
  38. }
  39. template <storm::dd::DdType Type, typename ValueType>
  40. DdJaniModelBuilder<Type, ValueType>::Options::Options(std::vector<std::shared_ptr<storm::logic::Formula const>> const& formulas) : buildAllLabels(false), buildAllRewardModels(false), rewardModelsToBuild(), constantDefinitions(), terminalStates(), negatedTerminalStates() {
  41. if (formulas.empty()) {
  42. this->buildAllRewardModels = true;
  43. } else {
  44. for (auto const& formula : formulas) {
  45. this->preserveFormula(*formula);
  46. }
  47. if (formulas.size() == 1) {
  48. this->setTerminalStatesFromFormula(*formulas.front());
  49. }
  50. }
  51. }
  52. template <storm::dd::DdType Type, typename ValueType>
  53. void DdJaniModelBuilder<Type, ValueType>::Options::preserveFormula(storm::logic::Formula const& formula) {
  54. // If we already had terminal states, we need to erase them.
  55. if (terminalStates) {
  56. terminalStates.reset();
  57. }
  58. if (negatedTerminalStates) {
  59. negatedTerminalStates.reset();
  60. }
  61. // If we are not required to build all reward models, we determine the reward models we need to build.
  62. if (!buildAllRewardModels) {
  63. std::set<std::string> referencedRewardModels = formula.getReferencedRewardModels();
  64. rewardModelsToBuild.insert(referencedRewardModels.begin(), referencedRewardModels.end());
  65. }
  66. // Extract all the labels used in the formula.
  67. std::vector<std::shared_ptr<storm::logic::AtomicLabelFormula const>> atomicLabelFormulas = formula.getAtomicLabelFormulas();
  68. for (auto const& formula : atomicLabelFormulas) {
  69. addLabel(formula->getLabel());
  70. }
  71. }
  72. template <storm::dd::DdType Type, typename ValueType>
  73. void DdJaniModelBuilder<Type, ValueType>::Options::setTerminalStatesFromFormula(storm::logic::Formula const& formula) {
  74. if (formula.isAtomicExpressionFormula()) {
  75. terminalStates = formula.asAtomicExpressionFormula().getExpression();
  76. } else if (formula.isEventuallyFormula()) {
  77. storm::logic::Formula const& sub = formula.asEventuallyFormula().getSubformula();
  78. if (sub.isAtomicExpressionFormula() || sub.isAtomicLabelFormula()) {
  79. this->setTerminalStatesFromFormula(sub);
  80. }
  81. } else if (formula.isUntilFormula()) {
  82. storm::logic::Formula const& right = formula.asUntilFormula().getRightSubformula();
  83. if (right.isAtomicExpressionFormula() || right.isAtomicLabelFormula()) {
  84. this->setTerminalStatesFromFormula(right);
  85. }
  86. storm::logic::Formula const& left = formula.asUntilFormula().getLeftSubformula();
  87. if (left.isAtomicExpressionFormula()) {
  88. negatedTerminalStates = left.asAtomicExpressionFormula().getExpression();
  89. }
  90. } else if (formula.isProbabilityOperatorFormula()) {
  91. storm::logic::Formula const& sub = formula.asProbabilityOperatorFormula().getSubformula();
  92. if (sub.isEventuallyFormula() || sub.isUntilFormula()) {
  93. this->setTerminalStatesFromFormula(sub);
  94. }
  95. }
  96. }
  97. template <storm::dd::DdType Type, typename ValueType>
  98. std::set<std::string> const& DdJaniModelBuilder<Type, ValueType>::Options::getRewardModelNames() const {
  99. return rewardModelsToBuild;
  100. }
  101. template <storm::dd::DdType Type, typename ValueType>
  102. bool DdJaniModelBuilder<Type, ValueType>::Options::isBuildAllRewardModelsSet() const {
  103. return buildAllRewardModels;
  104. }
  105. template <storm::dd::DdType Type, typename ValueType>
  106. bool DdJaniModelBuilder<Type, ValueType>::Options::isBuildAllLabelsSet() const {
  107. return buildAllLabels;
  108. }
  109. template <storm::dd::DdType Type, typename ValueType>
  110. void DdJaniModelBuilder<Type, ValueType>::Options::addLabel(std::string const& labelName) {
  111. STORM_LOG_THROW(!buildAllLabels, storm::exceptions::InvalidSettingsException, "Cannot add label, because all labels are built anyway.");
  112. labelNames.insert(labelName);
  113. }
  114. template <storm::dd::DdType Type, typename ValueType>
  115. struct CompositionVariables {
  116. CompositionVariables() : manager(std::make_shared<storm::dd::DdManager<Type>>()),
  117. variableToRowMetaVariableMap(std::make_shared<std::map<storm::expressions::Variable, storm::expressions::Variable>>()),
  118. rowExpressionAdapter(std::make_shared<storm::adapters::AddExpressionAdapter<Type>>(manager, variableToRowMetaVariableMap)),
  119. variableToColumnMetaVariableMap(std::make_shared<std::map<storm::expressions::Variable, storm::expressions::Variable>>()),
  120. columnExpressionAdapter(std::make_shared<storm::adapters::AddExpressionAdapter<Type>>(manager, variableToColumnMetaVariableMap)) {
  121. // Intentionally left empty.
  122. }
  123. std::shared_ptr<storm::dd::DdManager<Type>> manager;
  124. // The meta variables for the row encoding.
  125. std::set<storm::expressions::Variable> rowMetaVariables;
  126. std::shared_ptr<std::map<storm::expressions::Variable, storm::expressions::Variable>> variableToRowMetaVariableMap;
  127. std::shared_ptr<storm::adapters::AddExpressionAdapter<Type>> rowExpressionAdapter;
  128. // The meta variables for the column encoding.
  129. std::set<storm::expressions::Variable> columnMetaVariables;
  130. std::shared_ptr<std::map<storm::expressions::Variable, storm::expressions::Variable>> variableToColumnMetaVariableMap;
  131. std::shared_ptr<storm::adapters::AddExpressionAdapter<Type>> columnExpressionAdapter;
  132. // All pairs of row/column meta variables.
  133. std::vector<std::pair<storm::expressions::Variable, storm::expressions::Variable>> rowColumnMetaVariablePairs;
  134. // A mapping from automata to the meta variables encoding their location.
  135. std::map<std::string, storm::expressions::Variable> automatonToLocationVariableMap;
  136. std::map<std::string, std::pair<storm::expressions::Variable, storm::expressions::Variable>> automatonToLocationDdVariableMap;
  137. // A mapping from action indices to the meta variables used to encode these actions.
  138. std::map<uint64_t, storm::expressions::Variable> actionVariablesMap;
  139. // The meta variables used to encode the remaining nondeterminism.
  140. std::vector<storm::expressions::Variable> localNondeterminismVariables;
  141. // The meta variable used to distinguish Markovian from probabilistic choices in Markov automata.
  142. storm::expressions::Variable markovNondeterminismVariable;
  143. storm::dd::Bdd<Type> markovMarker;
  144. // The meta variables used to encode the actions and nondeterminism.
  145. std::set<storm::expressions::Variable> allNondeterminismVariables;
  146. // DDs representing the identity for each variable.
  147. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> variableToIdentityMap;
  148. // DDs representing the ranges of each variable.
  149. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> variableToRangeMap;
  150. // A set of all meta variables that correspond to global variables.
  151. std::set<storm::expressions::Variable> allGlobalVariables;
  152. // DDs representing the identity for each automaton.
  153. std::map<std::string, storm::dd::Add<Type, ValueType>> automatonToIdentityMap;
  154. // DDs representing the valid ranges of the variables of each automaton.
  155. std::map<std::string, storm::dd::Add<Type, ValueType>> automatonToRangeMap;
  156. // A DD representing the valid ranges of the global variables.
  157. storm::dd::Add<Type, ValueType> globalVariableRanges;
  158. };
  159. // A class responsible for creating the necessary variables for a subsequent composition of automata.
  160. template <storm::dd::DdType Type, typename ValueType>
  161. class CompositionVariableCreator : public storm::jani::CompositionVisitor {
  162. public:
  163. CompositionVariableCreator(storm::jani::Model const& model, storm::jani::CompositionInformation const& actionInformation) : model(model), automata(), actionInformation(actionInformation) {
  164. // Intentionally left empty.
  165. }
  166. CompositionVariables<Type, ValueType> create() {
  167. // First, check whether every automaton appears exactly once in the system composition. Simultaneously,
  168. // we determine the set of non-silent actions used by the composition.
  169. automata.clear();
  170. this->model.getSystemComposition().accept(*this, boost::none);
  171. 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.");
  172. STORM_LOG_THROW(!this->model.hasTransientEdgeDestinationAssignments(), storm::exceptions::InvalidArgumentException, "The symbolic JANI model builder currently does not support transient edge destination assignments.");
  173. // Then, check that the model does not contain non-transient unbounded integer or non-transient real variables.
  174. STORM_LOG_THROW(!this->model.getGlobalVariables().containsNonTransientUnboundedIntegerVariables(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model that contains non-transient global unbounded integer variables.");
  175. STORM_LOG_THROW(!this->model.getGlobalVariables().containsNonTransientRealVariables(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model that contains global non-transient real variables.");
  176. for (auto const& automaton : this->model.getAutomata()) {
  177. STORM_LOG_THROW(!automaton.getVariables().containsNonTransientUnboundedIntegerVariables(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model that contains non-transient unbounded integer variables in automaton '" << automaton.getName() << "'.");
  178. STORM_LOG_THROW(!automaton.getVariables().containsNonTransientRealVariables(), storm::exceptions::InvalidArgumentException, "Cannot build symbolic model from JANI model that contains non-transient real variables in automaton '" << automaton.getName() << "'.");
  179. }
  180. // Based on this assumption, we create the variables.
  181. return createVariables();
  182. }
  183. boost::any visit(storm::jani::AutomatonComposition const& composition, boost::any const& data) override {
  184. auto it = automata.find(composition.getAutomatonName());
  185. 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.");
  186. automata.insert(it, composition.getAutomatonName());
  187. return boost::none;
  188. }
  189. boost::any visit(storm::jani::ParallelComposition const& composition, boost::any const& data) override {
  190. for (auto const& subcomposition : composition.getSubcompositions()) {
  191. subcomposition->accept(*this, boost::none);
  192. }
  193. return boost::none;
  194. }
  195. private:
  196. CompositionVariables<Type, ValueType> createVariables() {
  197. CompositionVariables<Type, ValueType> result;
  198. for (auto const& nonSilentActionIndex : actionInformation.getNonSilentActionIndices()) {
  199. std::pair<storm::expressions::Variable, storm::expressions::Variable> variablePair = result.manager->addMetaVariable(actionInformation.getActionName(nonSilentActionIndex));
  200. result.actionVariablesMap[nonSilentActionIndex] = variablePair.first;
  201. result.allNondeterminismVariables.insert(variablePair.first);
  202. }
  203. // FIXME: check how many nondeterminism variables we should actually allocate.
  204. uint64_t numberOfNondeterminismVariables = this->model.getNumberOfAutomata();
  205. for (auto const& automaton : this->model.getAutomata()) {
  206. numberOfNondeterminismVariables += automaton.getNumberOfEdges();
  207. }
  208. for (uint_fast64_t i = 0; i < numberOfNondeterminismVariables; ++i) {
  209. std::pair<storm::expressions::Variable, storm::expressions::Variable> variablePair = result.manager->addMetaVariable("nondet" + std::to_string(i));
  210. result.localNondeterminismVariables.push_back(variablePair.first);
  211. result.allNondeterminismVariables.insert(variablePair.first);
  212. }
  213. if (this->model.getModelType() == storm::jani::ModelType::MA) {
  214. result.markovNondeterminismVariable = result.manager->addMetaVariable("markov").first;
  215. result.markovMarker = result.manager->getEncoding(result.markovNondeterminismVariable, 1);
  216. result.allNondeterminismVariables.insert(result.markovNondeterminismVariable);
  217. }
  218. for (auto const& automaton : this->model.getAutomata()) {
  219. // Start by creating a meta variable for the location of the automaton.
  220. storm::expressions::Variable locationExpressionVariable = model.getManager().declareFreshIntegerVariable(false, "loc");
  221. result.automatonToLocationVariableMap[automaton.getName()] = locationExpressionVariable;
  222. std::pair<storm::expressions::Variable, storm::expressions::Variable> variablePair = result.manager->addMetaVariable("l_" + automaton.getName(), 0, automaton.getNumberOfLocations() - 1);
  223. result.automatonToLocationDdVariableMap[automaton.getName()] = variablePair;
  224. result.rowColumnMetaVariablePairs.push_back(variablePair);
  225. result.variableToRowMetaVariableMap->emplace(locationExpressionVariable, variablePair.first);
  226. result.variableToColumnMetaVariableMap->emplace(locationExpressionVariable, variablePair.second);
  227. // Add the location variable to the row/column variables.
  228. result.rowMetaVariables.insert(variablePair.first);
  229. result.columnMetaVariables.insert(variablePair.second);
  230. // Add the legal range for the location variables.
  231. result.variableToRangeMap.emplace(variablePair.first, result.manager->getRange(variablePair.first));
  232. result.variableToRangeMap.emplace(variablePair.second, result.manager->getRange(variablePair.second));
  233. }
  234. // Create global variables.
  235. storm::dd::Bdd<Type> globalVariableRanges = result.manager->getBddOne();
  236. for (auto const& variable : this->model.getGlobalVariables()) {
  237. // Only create the variable if it's non-transient.
  238. if (variable.isTransient()) {
  239. continue;
  240. }
  241. createVariable(variable, result);
  242. globalVariableRanges &= result.manager->getRange(result.variableToRowMetaVariableMap->at(variable.getExpressionVariable()));
  243. }
  244. result.globalVariableRanges = globalVariableRanges.template toAdd<ValueType>();
  245. // Create the variables for the individual automata.
  246. for (auto const& automaton : this->model.getAutomata()) {
  247. storm::dd::Bdd<Type> identity = result.manager->getBddOne();
  248. storm::dd::Bdd<Type> range = result.manager->getBddOne();
  249. // Add the identity and ranges of the location variables to the ones of the automaton.
  250. std::pair<storm::expressions::Variable, storm::expressions::Variable> const& locationVariables = result.automatonToLocationDdVariableMap[automaton.getName()];
  251. storm::dd::Add<Type, ValueType> variableIdentity = result.manager->template getIdentity<ValueType>(locationVariables.first).equals(result.manager->template getIdentity<ValueType>(locationVariables.second)).template toAdd<ValueType>() * result.manager->getRange(locationVariables.first).template toAdd<ValueType>() * result.manager->getRange(locationVariables.second).template toAdd<ValueType>();
  252. identity &= variableIdentity.toBdd();
  253. range &= result.manager->getRange(locationVariables.first);
  254. // Then create variables for the variables of the automaton.
  255. for (auto const& variable : automaton.getVariables()) {
  256. // Only create the variable if it's non-transient.
  257. if (variable.isTransient()) {
  258. continue;
  259. }
  260. createVariable(variable, result);
  261. identity &= result.variableToIdentityMap.at(variable.getExpressionVariable()).toBdd();
  262. range &= result.manager->getRange(result.variableToRowMetaVariableMap->at(variable.getExpressionVariable()));
  263. }
  264. result.automatonToIdentityMap[automaton.getName()] = identity.template toAdd<ValueType>();
  265. result.automatonToRangeMap[automaton.getName()] = (range && globalVariableRanges).template toAdd<ValueType>();
  266. }
  267. return result;
  268. }
  269. void createVariable(storm::jani::Variable const& variable, CompositionVariables<Type, ValueType>& result) {
  270. if (variable.isBooleanVariable()) {
  271. createVariable(variable.asBooleanVariable(), result);
  272. } else if (variable.isBoundedIntegerVariable()) {
  273. createVariable(variable.asBoundedIntegerVariable(), result);
  274. } else {
  275. STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Invalid type of variable in JANI model.");
  276. }
  277. }
  278. void createVariable(storm::jani::BoundedIntegerVariable const& variable, CompositionVariables<Type, ValueType>& result) {
  279. int_fast64_t low = variable.getLowerBound().evaluateAsInt();
  280. int_fast64_t high = variable.getUpperBound().evaluateAsInt();
  281. std::pair<storm::expressions::Variable, storm::expressions::Variable> variablePair = result.manager->addMetaVariable(variable.getExpressionVariable().getName(), low, high);
  282. STORM_LOG_TRACE("Created meta variables for global integer variable: " << variablePair.first.getName() << " and " << variablePair.second.getName() << ".");
  283. result.rowMetaVariables.insert(variablePair.first);
  284. result.variableToRowMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.first);
  285. result.columnMetaVariables.insert(variablePair.second);
  286. result.variableToColumnMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.second);
  287. storm::dd::Add<Type, ValueType> variableIdentity = result.manager->template getIdentity<ValueType>(variablePair.first).equals(result.manager->template getIdentity<ValueType>(variablePair.second)).template toAdd<ValueType>() * result.manager->getRange(variablePair.first).template toAdd<ValueType>() * result.manager->getRange(variablePair.second).template toAdd<ValueType>();
  288. result.variableToIdentityMap.emplace(variable.getExpressionVariable(), variableIdentity);
  289. result.rowColumnMetaVariablePairs.push_back(variablePair);
  290. result.variableToRangeMap.emplace(variablePair.first, result.manager->getRange(variablePair.first));
  291. result.variableToRangeMap.emplace(variablePair.second, result.manager->getRange(variablePair.second));
  292. result.allGlobalVariables.insert(variable.getExpressionVariable());
  293. }
  294. void createVariable(storm::jani::BooleanVariable const& variable, CompositionVariables<Type, ValueType>& result) {
  295. std::pair<storm::expressions::Variable, storm::expressions::Variable> variablePair = result.manager->addMetaVariable(variable.getExpressionVariable().getName());
  296. STORM_LOG_TRACE("Created meta variables for global boolean variable: " << variablePair.first.getName() << " and " << variablePair.second.getName() << ".");
  297. result.rowMetaVariables.insert(variablePair.first);
  298. result.variableToRowMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.first);
  299. result.columnMetaVariables.insert(variablePair.second);
  300. result.variableToColumnMetaVariableMap->emplace(variable.getExpressionVariable(), variablePair.second);
  301. storm::dd::Add<Type, ValueType> variableIdentity = result.manager->template getIdentity<ValueType>(variablePair.first).equals(result.manager->template getIdentity<ValueType>(variablePair.second)).template toAdd<ValueType>();
  302. result.variableToIdentityMap.emplace(variable.getExpressionVariable(), variableIdentity);
  303. result.variableToRangeMap.emplace(variablePair.first, result.manager->getRange(variablePair.first));
  304. result.variableToRangeMap.emplace(variablePair.second, result.manager->getRange(variablePair.second));
  305. result.rowColumnMetaVariablePairs.push_back(variablePair);
  306. result.allGlobalVariables.insert(variable.getExpressionVariable());
  307. }
  308. storm::jani::Model const& model;
  309. std::set<std::string> automata;
  310. storm::jani::CompositionInformation actionInformation;
  311. };
  312. template <storm::dd::DdType Type, typename ValueType>
  313. struct ComposerResult {
  314. ComposerResult(storm::dd::Add<Type, ValueType> const& transitions, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientLocationAssignments, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientEdgeAssignments, storm::dd::Bdd<Type> const& illegalFragment, uint64_t numberOfNondeterminismVariables = 0) : transitions(transitions), transientLocationAssignments(transientLocationAssignments), transientEdgeAssignments(transientEdgeAssignments), illegalFragment(illegalFragment), numberOfNondeterminismVariables(numberOfNondeterminismVariables) {
  315. // Intentionally left empty.
  316. }
  317. storm::dd::Add<Type, ValueType> transitions;
  318. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientLocationAssignments;
  319. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  320. storm::dd::Bdd<Type> illegalFragment;
  321. uint64_t numberOfNondeterminismVariables;
  322. };
  323. // A class that is responsible for performing the actual composition. This
  324. template <storm::dd::DdType Type, typename ValueType>
  325. class SystemComposer : public storm::jani::CompositionVisitor {
  326. public:
  327. SystemComposer(storm::jani::Model const& model, CompositionVariables<Type, ValueType> const& variables, std::vector<storm::expressions::Variable> const& transientVariables) : model(model), variables(variables), transientVariables(transientVariables) {
  328. // Intentionally left empty.
  329. }
  330. virtual ComposerResult<Type, ValueType> compose() = 0;
  331. protected:
  332. // The model that is referred to by the composition.
  333. storm::jani::Model const& model;
  334. // The variable to use when building an automaton.
  335. CompositionVariables<Type, ValueType> const& variables;
  336. // The transient variables to consider during system composition.
  337. std::vector<storm::expressions::Variable> transientVariables;
  338. };
  339. // This structure represents an edge destination.
  340. template <storm::dd::DdType Type, typename ValueType>
  341. struct EdgeDestinationDd {
  342. EdgeDestinationDd(storm::dd::Add<Type, ValueType> const& transitions, std::set<storm::expressions::Variable> const& writtenGlobalVariables = {}) : transitions(transitions), writtenGlobalVariables(writtenGlobalVariables) {
  343. // Intentionally left empty.
  344. }
  345. storm::dd::Add<Type, ValueType> transitions;
  346. std::set<storm::expressions::Variable> writtenGlobalVariables;
  347. };
  348. template <storm::dd::DdType Type, typename ValueType>
  349. EdgeDestinationDd<Type, ValueType> buildEdgeDestinationDd(storm::jani::Automaton const& automaton, storm::jani::EdgeDestination const& destination, storm::dd::Add<Type, ValueType> const& guard, CompositionVariables<Type, ValueType> const& variables) {
  350. storm::dd::Add<Type, ValueType> transitions = variables.rowExpressionAdapter->translateExpression(destination.getProbability());
  351. STORM_LOG_TRACE("Translating edge destination.");
  352. // Iterate over all assignments (boolean and integer) and build the DD for it.
  353. std::set<storm::expressions::Variable> assignedVariables;
  354. for (auto const& assignment : destination.getOrderedAssignments().getNonTransientAssignments()) {
  355. // Record the variable as being written.
  356. STORM_LOG_TRACE("Assigning to variable " << variables.variableToRowMetaVariableMap->at(assignment.getExpressionVariable()).getName());
  357. assignedVariables.insert(assignment.getExpressionVariable());
  358. // Translate the written variable.
  359. auto const& primedMetaVariable = variables.variableToColumnMetaVariableMap->at(assignment.getExpressionVariable());
  360. storm::dd::Add<Type, ValueType> writtenVariable = variables.manager->template getIdentity<ValueType>(primedMetaVariable);
  361. // Translate the expression that is being assigned.
  362. storm::dd::Add<Type, ValueType> assignedExpression = variables.rowExpressionAdapter->translateExpression(assignment.getAssignedExpression());
  363. // Combine the assigned expression with the guard.
  364. storm::dd::Add<Type, ValueType> result = assignedExpression * guard;
  365. // Combine the variable and the assigned expression.
  366. result = result.equals(writtenVariable).template toAdd<ValueType>();
  367. result *= guard;
  368. // Restrict the transitions to the range of the written variable.
  369. result = result * variables.variableToRangeMap.at(primedMetaVariable).template toAdd<ValueType>();
  370. // Combine the assignment DDs.
  371. transitions *= result;
  372. }
  373. // Compute the set of assigned global variables.
  374. std::set<storm::expressions::Variable> assignedGlobalVariables;
  375. std::set_intersection(assignedVariables.begin(), assignedVariables.end(), variables.allGlobalVariables.begin(), variables.allGlobalVariables.end(), std::inserter(assignedGlobalVariables, assignedGlobalVariables.begin()));
  376. // All unassigned boolean variables need to keep their value.
  377. for (storm::jani::BooleanVariable const& variable : automaton.getVariables().getBooleanVariables()) {
  378. if (assignedVariables.find(variable.getExpressionVariable()) == assignedVariables.end()) {
  379. STORM_LOG_TRACE("Multiplying identity of variable " << variable.getName());
  380. transitions *= variables.variableToIdentityMap.at(variable.getExpressionVariable());
  381. }
  382. }
  383. // All unassigned integer variables need to keep their value.
  384. for (storm::jani::BoundedIntegerVariable const& variable : automaton.getVariables().getBoundedIntegerVariables()) {
  385. if (assignedVariables.find(variable.getExpressionVariable()) == assignedVariables.end()) {
  386. STORM_LOG_TRACE("Multiplying identity of variable " << variable.getName());
  387. transitions *= variables.variableToIdentityMap.at(variable.getExpressionVariable());
  388. }
  389. }
  390. transitions *= variables.manager->getEncoding(variables.automatonToLocationDdVariableMap.at(automaton.getName()).second, destination.getLocationIndex()).template toAdd<ValueType>();
  391. return EdgeDestinationDd<Type, ValueType>(transitions, assignedGlobalVariables);
  392. }
  393. template <storm::dd::DdType Type, typename ValueType>
  394. storm::dd::Add<Type, ValueType> encodeAction(boost::optional<uint64_t> const& actionIndex, CompositionVariables<Type, ValueType> const& variables) {
  395. storm::dd::Add<Type, ValueType> encoding = variables.manager->template getAddOne<ValueType>();
  396. for (auto it = variables.actionVariablesMap.rbegin(), ite = variables.actionVariablesMap.rend(); it != ite; ++it) {
  397. if (actionIndex && it->first == actionIndex.get()) {
  398. encoding *= variables.manager->getEncoding(it->second, 1).template toAdd<ValueType>();
  399. } else {
  400. encoding *= variables.manager->getEncoding(it->second, 0).template toAdd<ValueType>();
  401. }
  402. }
  403. return encoding;
  404. }
  405. template <storm::dd::DdType Type, typename ValueType>
  406. storm::dd::Add<Type, ValueType> encodeIndex(uint64_t index, uint64_t localNondeterminismVariableOffset, uint64_t numberOfLocalNondeterminismVariables, CompositionVariables<Type, ValueType> const& variables) {
  407. storm::dd::Add<Type, ValueType> result = variables.manager->template getAddZero<ValueType>();
  408. std::map<storm::expressions::Variable, int_fast64_t> metaVariableNameToValueMap;
  409. for (uint_fast64_t i = 0; i < numberOfLocalNondeterminismVariables; ++i) {
  410. if (index & (1ull << (numberOfLocalNondeterminismVariables - i - 1))) {
  411. metaVariableNameToValueMap.emplace(variables.localNondeterminismVariables[localNondeterminismVariableOffset + i], 1);
  412. } else {
  413. metaVariableNameToValueMap.emplace(variables.localNondeterminismVariables[localNondeterminismVariableOffset + i], 0);
  414. }
  415. }
  416. result.setValue(metaVariableNameToValueMap, 1);
  417. return result;
  418. }
  419. template <storm::dd::DdType Type, typename ValueType>
  420. class CombinedEdgesSystemComposer : public SystemComposer<Type, ValueType> {
  421. public:
  422. // This structure represents an edge.
  423. struct EdgeDd {
  424. EdgeDd(bool isMarkovian, storm::dd::Add<Type> const& guard, storm::dd::Add<Type, ValueType> const& transitions, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientEdgeAssignments, std::set<storm::expressions::Variable> const& writtenGlobalVariables) : isMarkovian(isMarkovian), guard(guard), transitions(transitions), transientEdgeAssignments(transientEdgeAssignments), variableToWritingFragment() {
  425. // Convert the set of written variables to a mapping from variable to the writing fragments.
  426. for (auto const& variable : writtenGlobalVariables) {
  427. variableToWritingFragment[variable] = guard.toBdd();
  428. }
  429. }
  430. EdgeDd(bool isMarkovian, storm::dd::Add<Type> const& guard, storm::dd::Add<Type, ValueType> const& transitions, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientEdgeAssignments, std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> const& variableToWritingFragment) : isMarkovian(isMarkovian), guard(guard), transitions(transitions), transientEdgeAssignments(transientEdgeAssignments), variableToWritingFragment(variableToWritingFragment) {
  431. // Intentionally left empty.
  432. }
  433. // A flag storing whether this edge is a Markovian one (i.e. one with a rate).
  434. bool isMarkovian;
  435. // A DD that represents all states that have this edge enabled.
  436. storm::dd::Add<Type, ValueType> guard;
  437. // A DD that represents the transitions of this edge.
  438. storm::dd::Add<Type, ValueType> transitions;
  439. // A mapping from transient variables to the DDs representing their value assignments.
  440. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  441. // A mapping of variables to the variables to the fragment of transitions that is writing the corresponding variable.
  442. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> variableToWritingFragment;
  443. };
  444. // This structure represents an edge.
  445. struct ActionDd {
  446. ActionDd(storm::dd::Add<Type> const& guard = storm::dd::Add<Type>(), storm::dd::Add<Type, ValueType> const& transitions = storm::dd::Add<Type, ValueType>(), std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientEdgeAssignments = {}, std::pair<uint64_t, uint64_t> localNondeterminismVariables = std::pair<uint64_t, uint64_t>(0, 0), std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> const& variableToWritingFragment = {}, storm::dd::Bdd<Type> const& illegalFragment = storm::dd::Bdd<Type>()) : guard(guard), transitions(transitions), transientEdgeAssignments(transientEdgeAssignments), localNondeterminismVariables(localNondeterminismVariables), variableToWritingFragment(variableToWritingFragment), illegalFragment(illegalFragment), inputEnabled(false) {
  447. // Intentionally left empty.
  448. }
  449. uint64_t getLowestLocalNondeterminismVariable() const {
  450. return localNondeterminismVariables.first;
  451. }
  452. uint64_t getHighestLocalNondeterminismVariable() const {
  453. return localNondeterminismVariables.second;
  454. }
  455. std::pair<uint64_t, uint64_t> const& getLocalNondeterminismVariables() const {
  456. return localNondeterminismVariables;
  457. }
  458. ActionDd multiplyTransitions(storm::dd::Add<Type, ValueType> const& factor) const {
  459. return ActionDd(guard, transitions * factor, transientEdgeAssignments, localNondeterminismVariables, variableToWritingFragment, illegalFragment);
  460. }
  461. bool isInputEnabled() const {
  462. return inputEnabled;
  463. }
  464. void setIsInputEnabled() {
  465. inputEnabled = true;
  466. }
  467. // A DD that represents all states that have this edge enabled.
  468. storm::dd::Add<Type, ValueType> guard;
  469. // A DD that represents the transitions of this edge.
  470. storm::dd::Add<Type, ValueType> transitions;
  471. // A mapping from transient variables to their assignments.
  472. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  473. // The local nondeterminism variables used by this action DD, given as the lowest
  474. std::pair<uint64_t, uint64_t> localNondeterminismVariables;
  475. // A mapping from global variables to a DD that characterizes choices (nondeterminism variables) in
  476. // states that write to this global variable.
  477. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> variableToWritingFragment;
  478. // A DD characterizing the fragment of the states satisfying the guard that are illegal because
  479. // there are synchronizing edges enabled that write to the same global variable.
  480. storm::dd::Bdd<Type> illegalFragment;
  481. // A flag storing whether this action is input-enabled.
  482. bool inputEnabled;
  483. };
  484. // This structure represents a subcomponent of a composition.
  485. struct AutomatonDd {
  486. AutomatonDd(storm::dd::Add<Type, ValueType> const& identity, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientLocationAssignments = {}) : actionIndexToAction(), transientLocationAssignments(transientLocationAssignments), identity(identity), localNondeterminismVariables(std::make_pair<uint64_t, uint64_t>(0, 0)) {
  487. // Intentionally left empty.
  488. }
  489. uint64_t getLowestLocalNondeterminismVariable() const {
  490. return localNondeterminismVariables.first;
  491. }
  492. void setLowestLocalNondeterminismVariable(uint64_t newValue) {
  493. localNondeterminismVariables.first = newValue;
  494. }
  495. uint64_t getHighestLocalNondeterminismVariable() const {
  496. return localNondeterminismVariables.second;
  497. }
  498. void setHighestLocalNondeterminismVariable(uint64_t newValue) {
  499. localNondeterminismVariables.second = newValue;
  500. }
  501. void extendLocalNondeterminismVariables(std::pair<uint64_t, uint64_t> const& localNondeterminismVariables) {
  502. setLowestLocalNondeterminismVariable(std::min(localNondeterminismVariables.first, getLowestLocalNondeterminismVariable()));
  503. setHighestLocalNondeterminismVariable(std::max(localNondeterminismVariables.second, getHighestLocalNondeterminismVariable()));
  504. }
  505. // A mapping from action indices to the action DDs.
  506. std::map<uint64_t, ActionDd> actionIndexToAction;
  507. // A mapping from transient variables to their location-based transient assignment values.
  508. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientLocationAssignments;
  509. // The identity of the automaton's variables.
  510. storm::dd::Add<Type, ValueType> identity;
  511. // The local nondeterminism variables used by this action DD, given as the lowest and highest variable index.
  512. std::pair<uint64_t, uint64_t> localNondeterminismVariables;
  513. };
  514. CombinedEdgesSystemComposer(storm::jani::Model const& model, storm::jani::CompositionInformation const& actionInformation, CompositionVariables<Type, ValueType> const& variables, std::vector<storm::expressions::Variable> const& transientVariables) : SystemComposer<Type, ValueType>(model, variables, transientVariables), actionInformation(actionInformation) {
  515. // Intentionally left empty.
  516. }
  517. storm::jani::CompositionInformation const& actionInformation;
  518. ComposerResult<Type, ValueType> compose() override {
  519. std::map<uint64_t, uint64_t> actionIndexToLocalNondeterminismVariableOffset;
  520. for (auto const& actionIndex : actionInformation.getNonSilentActionIndices()) {
  521. actionIndexToLocalNondeterminismVariableOffset[actionIndex] = 0;
  522. }
  523. actionIndexToLocalNondeterminismVariableOffset[storm::jani::Model::SILENT_ACTION_INDEX] = 0;
  524. AutomatonDd globalAutomaton = boost::any_cast<AutomatonDd>(this->model.getSystemComposition().accept(*this, actionIndexToLocalNondeterminismVariableOffset));
  525. return buildSystemFromAutomaton(globalAutomaton);
  526. }
  527. boost::any visit(storm::jani::AutomatonComposition const& composition, boost::any const& data) override {
  528. std::map<uint64_t, uint64_t> const& actionIndexToLocalNondeterminismVariableOffset = boost::any_cast<std::map<uint_fast64_t, uint_fast64_t> const&>(data);
  529. std::set<uint64_t> inputEnabledActionIndices;
  530. for (auto const& actionName : composition.getInputEnabledActions()) {
  531. inputEnabledActionIndices.insert(actionInformation.getActionIndex(actionName));
  532. }
  533. return buildAutomatonDd(composition.getAutomatonName(), actionIndexToLocalNondeterminismVariableOffset, inputEnabledActionIndices);
  534. }
  535. boost::any visit(storm::jani::ParallelComposition const& composition, boost::any const& data) override {
  536. std::map<uint64_t, uint64_t> const& actionIndexToLocalNondeterminismVariableOffset = boost::any_cast<std::map<uint64_t, uint64_t> const&>(data);
  537. std::vector<AutomatonDd> subautomata;
  538. for (uint64_t subcompositionIndex = 0; subcompositionIndex < composition.getNumberOfSubcompositions(); ++subcompositionIndex) {
  539. // Prepare the new offset mapping.
  540. std::map<uint64_t, uint64_t> newSynchronizingActionToOffsetMap = actionIndexToLocalNondeterminismVariableOffset;
  541. if (subcompositionIndex == 0) {
  542. for (auto const& synchVector : composition.getSynchronizationVectors()) {
  543. auto it = actionIndexToLocalNondeterminismVariableOffset.find(actionInformation.getActionIndex(synchVector.getOutput()));
  544. STORM_LOG_THROW(it != actionIndexToLocalNondeterminismVariableOffset.end(), storm::exceptions::InvalidArgumentException, "Invalid action " << synchVector.getOutput() << ".");
  545. if (synchVector.getInput(0) != storm::jani::SynchronizationVector::NO_ACTION_INPUT) {
  546. newSynchronizingActionToOffsetMap[actionInformation.getActionIndex(synchVector.getInput(0))] = it->second;
  547. }
  548. }
  549. } else {
  550. // Based on the previous results, we need to update the offsets.
  551. for (auto const& synchVector : composition.getSynchronizationVectors()) {
  552. if (synchVector.getInput(subcompositionIndex) != storm::jani::SynchronizationVector::NO_ACTION_INPUT) {
  553. boost::optional<uint64_t> previousActionPosition = synchVector.getPositionOfPrecedingParticipatingAction(subcompositionIndex);
  554. if (previousActionPosition) {
  555. AutomatonDd const& previousAutomatonDd = subautomata[previousActionPosition.get()];
  556. std::string const& previousAction = synchVector.getInput(previousActionPosition.get());
  557. auto it = previousAutomatonDd.actionIndexToAction.find(actionInformation.getActionIndex(previousAction));
  558. if (it != previousAutomatonDd.actionIndexToAction.end()) {
  559. newSynchronizingActionToOffsetMap[actionInformation.getActionIndex(synchVector.getInput(subcompositionIndex))] = it->second.getHighestLocalNondeterminismVariable();
  560. } else {
  561. STORM_LOG_ASSERT(false, "Subcomposition does not have action that is mentioned in parallel composition.");
  562. }
  563. }
  564. }
  565. }
  566. }
  567. // Build the DD for the next element of the composition wrt. to the current offset mapping.
  568. subautomata.push_back(boost::any_cast<AutomatonDd>(composition.getSubcomposition(subcompositionIndex).accept(*this, newSynchronizingActionToOffsetMap)));
  569. }
  570. return composeInParallel(subautomata, composition.getSynchronizationVectors());
  571. }
  572. private:
  573. AutomatonDd composeInParallel(std::vector<AutomatonDd> const& subautomata, std::vector<storm::jani::SynchronizationVector> const& synchronizationVectors) {
  574. typedef storm::dd::Add<Type, ValueType> IdentityAdd;
  575. typedef std::pair<ActionDd, IdentityAdd> ActionAndAutomatonIdentity;
  576. typedef std::vector<ActionAndAutomatonIdentity> ActionAndAutomatonIdentities;
  577. typedef std::vector<boost::optional<std::pair<ActionAndAutomatonIdentities, IdentityAdd>>> SynchronizationVectorActionsAndIdentities;
  578. AutomatonDd result(this->variables.manager->template getAddOne<ValueType>());
  579. std::map<uint64_t, std::vector<ActionDd>> nonSynchronizingActions;
  580. SynchronizationVectorActionsAndIdentities synchronizationVectorActions(synchronizationVectors.size(), boost::none);
  581. for (uint64_t automatonIndex = 0; automatonIndex < subautomata.size(); ++automatonIndex) {
  582. AutomatonDd const& subautomaton = subautomata[automatonIndex];
  583. // Add the transient assignments from the new subautomaton.
  584. addToTransientAssignmentMap(result.transientLocationAssignments, subautomaton.transientLocationAssignments);
  585. // Initilize the used local nondeterminism variables appropriately.
  586. if (automatonIndex == 0) {
  587. result.setLowestLocalNondeterminismVariable(subautomaton.getLowestLocalNondeterminismVariable());
  588. result.setHighestLocalNondeterminismVariable(subautomaton.getHighestLocalNondeterminismVariable());
  589. }
  590. // Compose the actions according to the synchronization vectors.
  591. std::set<uint64_t> actionsInSynch;
  592. for (uint64_t synchVectorIndex = 0; synchVectorIndex < synchronizationVectors.size(); ++synchVectorIndex) {
  593. auto const& synchVector = synchronizationVectors[synchVectorIndex];
  594. if (synchVector.isNoActionInput(synchVector.getInput(automatonIndex))) {
  595. if (automatonIndex == 0) {
  596. // Create a new action that is the identity over the first automaton.
  597. synchronizationVectorActions[synchVectorIndex] = std::make_pair(ActionAndAutomatonIdentities{std::make_pair(ActionDd(this->variables.manager->template getAddOne<ValueType>(), subautomaton.identity, {}, subautomaton.localNondeterminismVariables, {}, this->variables.manager->getBddZero()), subautomaton.identity)}, this->variables.manager->template getAddOne<ValueType>());
  598. } else {
  599. // If there is no action in the output spot, this means that some other subcomposition did
  600. // not provide the action necessary for the synchronization vector to resolve.
  601. if (synchronizationVectorActions[synchVectorIndex]) {
  602. synchronizationVectorActions[synchVectorIndex].get().second *= subautomaton.identity;
  603. }
  604. }
  605. } else {
  606. // Determine the indices of input (at the current automaton position) and the output.
  607. uint64_t inputActionIndex = actionInformation.getActionIndex(synchVector.getInput(automatonIndex));
  608. actionsInSynch.insert(inputActionIndex);
  609. // Either set the action (if it's the first of the ones to compose) or compose the actions directly.
  610. if (automatonIndex == 0) {
  611. // If the action cannot be found, the particular spot in the output will be left empty.
  612. auto inputActionIt = subautomaton.actionIndexToAction.find(inputActionIndex);
  613. if (inputActionIt != subautomaton.actionIndexToAction.end()) {
  614. synchronizationVectorActions[synchVectorIndex] = std::make_pair(ActionAndAutomatonIdentities{std::make_pair(inputActionIt->second, subautomaton.identity)}, this->variables.manager->template getAddOne<ValueType>());
  615. }
  616. } else {
  617. // If there is no action in the output spot, this means that some other subcomposition did
  618. // not provide the action necessary for the synchronization vector to resolve.
  619. if (synchronizationVectorActions[synchVectorIndex]) {
  620. auto inputActionIt = subautomaton.actionIndexToAction.find(inputActionIndex);
  621. if (inputActionIt != subautomaton.actionIndexToAction.end()) {
  622. synchronizationVectorActions[synchVectorIndex].get().first.push_back(std::make_pair(inputActionIt->second, subautomaton.identity));
  623. } else {
  624. // If the current subcomposition does not provide the required action for the synchronization
  625. // vector, we clear the action.
  626. synchronizationVectorActions[synchVectorIndex] = boost::none;
  627. }
  628. }
  629. }
  630. }
  631. }
  632. // Now treat all unsynchronizing actions.
  633. if (automatonIndex == 0) {
  634. // Since it's the first automaton, there is nothing to combine.
  635. for (auto const& action : subautomaton.actionIndexToAction) {
  636. if (actionsInSynch.find(action.first) == actionsInSynch.end()) {
  637. nonSynchronizingActions[action.first].push_back(action.second);
  638. }
  639. }
  640. } else {
  641. // Extend all other non-synchronizing actions with the identity of the current subautomaton.
  642. for (auto& actions : nonSynchronizingActions) {
  643. for (auto& action : actions.second) {
  644. STORM_LOG_TRACE("Extending action '" << actionInformation.getActionName(actions.first) << "' with identity of next composition.");
  645. action.transitions *= subautomaton.identity;
  646. }
  647. }
  648. // Extend the actions of the current subautomaton with the identity of the previous system and
  649. // add it to the overall non-synchronizing action result.
  650. for (auto const& action : subautomaton.actionIndexToAction) {
  651. if (actionsInSynch.find(action.first) == actionsInSynch.end()) {
  652. STORM_LOG_TRACE("Adding action " << actionInformation.getActionName(action.first) << " to non-synchronizing actions and multiply it with system identity.");
  653. nonSynchronizingActions[action.first].push_back(action.second.multiplyTransitions(result.identity));
  654. }
  655. }
  656. }
  657. // Finally, construct combined identity.
  658. result.identity *= subautomaton.identity;
  659. }
  660. // Add the results of the synchronization vectors to that of the non-synchronizing actions.
  661. for (uint64_t synchVectorIndex = 0; synchVectorIndex < synchronizationVectors.size(); ++synchVectorIndex) {
  662. auto const& synchVector = synchronizationVectors[synchVectorIndex];
  663. // If there is an action resulting from this combination of actions, add it to the output action.
  664. if (synchronizationVectorActions[synchVectorIndex]) {
  665. uint64_t outputActionIndex = actionInformation.getActionIndex(synchVector.getOutput());
  666. nonSynchronizingActions[outputActionIndex].push_back(combineSynchronizingActions(synchronizationVectorActions[synchVectorIndex].get().first, synchronizationVectorActions[synchVectorIndex].get().second));
  667. }
  668. }
  669. // Now that we have built the individual action DDs for all resulting actions, we need to combine them
  670. // in an unsynchronizing way.
  671. for (auto const& nonSynchronizingActionDds : nonSynchronizingActions) {
  672. std::vector<ActionDd> const& actionDds = nonSynchronizingActionDds.second;
  673. if (actionDds.size() > 1) {
  674. ActionDd combinedAction = combineUnsynchronizedActions(actionDds);
  675. result.actionIndexToAction[nonSynchronizingActionDds.first] = combinedAction;
  676. result.extendLocalNondeterminismVariables(combinedAction.getLocalNondeterminismVariables());
  677. } else {
  678. result.actionIndexToAction[nonSynchronizingActionDds.first] = actionDds.front();
  679. result.extendLocalNondeterminismVariables(actionDds.front().getLocalNondeterminismVariables());
  680. }
  681. }
  682. return result;
  683. }
  684. ActionDd combineSynchronizingActions(std::vector<std::pair<ActionDd, storm::dd::Add<Type, ValueType>>> const& actionsAndIdentities, storm::dd::Add<Type, ValueType> const& nonSynchronizingAutomataIdentities) {
  685. // If there is just one action, no need to combine anything.
  686. if (actionsAndIdentities.size() == 1) {
  687. return actionsAndIdentities.front().first;
  688. }
  689. // If there are only input-enabled actions, we also need to build the disjunction of the guards.
  690. bool allActionsInputEnabled = true;
  691. for (auto const& actionIdentityPair : actionsAndIdentities) {
  692. auto const& action = actionIdentityPair.first;
  693. if (!action.isInputEnabled()) {
  694. allActionsInputEnabled = false;
  695. }
  696. }
  697. boost::optional<storm::dd::Bdd<Type>> guardDisjunction;
  698. if (allActionsInputEnabled) {
  699. guardDisjunction = this->variables.manager->getBddZero();
  700. }
  701. // Otherwise, construct the synchronization.
  702. storm::dd::Bdd<Type> illegalFragment = this->variables.manager->getBddZero();
  703. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> globalVariableToWritingFragment;
  704. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> globalVariableToWritingFragmentWithoutNondeterminism;
  705. storm::dd::Bdd<Type> inputEnabledGuard = this->variables.manager->getBddOne();
  706. storm::dd::Add<Type, ValueType> transitions = this->variables.manager->template getAddOne<ValueType>();
  707. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  708. uint64_t lowestNondeterminismVariable = actionsAndIdentities.front().first.getLowestLocalNondeterminismVariable();
  709. uint64_t highestNondeterminismVariable = actionsAndIdentities.front().first.getHighestLocalNondeterminismVariable();
  710. storm::dd::Bdd<Type> newIllegalFragment = this->variables.manager->getBddZero();
  711. for (auto const& actionIdentityPair : actionsAndIdentities) {
  712. auto const& action = actionIdentityPair.first;
  713. storm::dd::Bdd<Type> actionGuard = action.guard.toBdd();
  714. if (guardDisjunction) {
  715. guardDisjunction.get() |= actionGuard;
  716. }
  717. lowestNondeterminismVariable = std::min(lowestNondeterminismVariable, action.getLowestLocalNondeterminismVariable());
  718. highestNondeterminismVariable = std::max(highestNondeterminismVariable, action.getHighestLocalNondeterminismVariable());
  719. transientEdgeAssignments = joinTransientAssignmentMaps(transientEdgeAssignments, action.transientEdgeAssignments);
  720. if (action.isInputEnabled()) {
  721. // If the action is input-enabled, we add self-loops to all states.
  722. transitions *= actionGuard.ite(action.transitions, encodeIndex(0, action.getLowestLocalNondeterminismVariable(), action.getHighestLocalNondeterminismVariable() - action.getLowestLocalNondeterminismVariable(), this->variables) * actionIdentityPair.second);
  723. } else {
  724. transitions *= action.transitions;
  725. }
  726. // Create a set of variables that is used as nondeterminism variables in this action.
  727. auto nondetVariables = std::set<storm::expressions::Variable>(this->variables.localNondeterminismVariables.begin() + action.getLowestLocalNondeterminismVariable(), this->variables.localNondeterminismVariables.begin() + action.getHighestLocalNondeterminismVariable());
  728. for (auto const& entry : action.variableToWritingFragment) {
  729. storm::dd::Bdd<Type> guardedWritingFragment = inputEnabledGuard && entry.second;
  730. // Check whether there already is an entry for this variable in the mapping of global variables
  731. // to their writing fragments.
  732. auto globalFragmentIt = globalVariableToWritingFragment.find(entry.first);
  733. if (globalFragmentIt != globalVariableToWritingFragment.end()) {
  734. // If there is, take the conjunction of the entries and also of their versions without nondeterminism
  735. // variables.
  736. globalFragmentIt->second &= guardedWritingFragment;
  737. illegalFragment |= globalVariableToWritingFragmentWithoutNondeterminism[entry.first] && guardedWritingFragment.existsAbstract(nondetVariables);
  738. globalVariableToWritingFragmentWithoutNondeterminism[entry.first] |= guardedWritingFragment.existsAbstract(nondetVariables);
  739. } else {
  740. // If not, create the entry and also create a version of the entry that abstracts from the
  741. // used nondeterminism variables.
  742. globalVariableToWritingFragment[entry.first] = guardedWritingFragment;
  743. globalVariableToWritingFragmentWithoutNondeterminism[entry.first] = guardedWritingFragment.existsAbstract(nondetVariables);
  744. }
  745. // Join all individual illegal fragments so we can see whether any of these elements lie in the
  746. // conjunction of all guards.
  747. illegalFragment |= action.illegalFragment;
  748. }
  749. // Now go through all fragments that are not written by the current action and join them with the
  750. // guard of the current action if the current action is not input enabled.
  751. for (auto& entry : globalVariableToWritingFragment) {
  752. if (action.variableToWritingFragment.find(entry.first) == action.variableToWritingFragment.end() && !action.isInputEnabled()) {
  753. entry.second &= actionGuard;
  754. }
  755. }
  756. if (!action.isInputEnabled()) {
  757. inputEnabledGuard &= actionGuard;
  758. }
  759. }
  760. // If all actions were input-enabled, we need to constrain the transitions with the disjunction of all
  761. // guards to make sure there are not transitions resulting from input enabledness alone.
  762. if (allActionsInputEnabled) {
  763. inputEnabledGuard &= guardDisjunction.get();
  764. transitions *= guardDisjunction.get().template toAdd<ValueType>();
  765. }
  766. // Cut the union of the illegal fragments to the conjunction of the guards since only these states have
  767. // such a combined transition.
  768. illegalFragment &= inputEnabledGuard;
  769. return ActionDd(inputEnabledGuard.template toAdd<ValueType>(), transitions * nonSynchronizingAutomataIdentities, transientEdgeAssignments, std::make_pair(lowestNondeterminismVariable, highestNondeterminismVariable), globalVariableToWritingFragment, illegalFragment);
  770. }
  771. ActionDd combineUnsynchronizedActions(ActionDd action1, ActionDd action2, storm::dd::Add<Type, ValueType> const& identity1, storm::dd::Add<Type, ValueType> const& identity2) {
  772. // First extend the action DDs by the other identities.
  773. STORM_LOG_TRACE("Multiplying identities to combine unsynchronized actions.");
  774. action1.transitions = action1.transitions * identity2;
  775. action2.transitions = action2.transitions * identity1;
  776. // Then combine the extended action DDs.
  777. return combineUnsynchronizedActions(action1, action2);
  778. }
  779. ActionDd combineUnsynchronizedActions(ActionDd action1, ActionDd action2) {
  780. return combineUnsynchronizedActions({action1, action2});
  781. }
  782. ActionDd combineUnsynchronizedActions(std::vector<ActionDd> actions) {
  783. STORM_LOG_TRACE("Combining unsynchronized actions.");
  784. if (this->model.getModelType() == storm::jani::ModelType::DTMC || this->model.getModelType() == storm::jani::ModelType::CTMC) {
  785. auto actionIt = actions.begin();
  786. ActionDd result(*actionIt);
  787. for (++actionIt; actionIt != actions.end(); ++actionIt) {
  788. result = ActionDd(result.guard + actionIt->guard, result.transitions + actionIt->transitions, joinTransientAssignmentMaps(result.transientEdgeAssignments, actionIt->transientEdgeAssignments), std::make_pair<uint64_t, uint64_t>(0, 0), joinVariableWritingFragmentMaps(result.variableToWritingFragment, actionIt->variableToWritingFragment), result.illegalFragment || actionIt->illegalFragment);
  789. }
  790. return result;
  791. } else if (this->model.getModelType() == storm::jani::ModelType::MDP) {
  792. // Ensure that all actions start at the same local nondeterminism variable.
  793. uint_fast64_t lowestLocalNondeterminismVariable = actions.front().getLowestLocalNondeterminismVariable();
  794. uint_fast64_t highestLocalNondeterminismVariable = actions.front().getHighestLocalNondeterminismVariable();
  795. for (auto const& action : actions) {
  796. STORM_LOG_ASSERT(action.getLowestLocalNondeterminismVariable() == lowestLocalNondeterminismVariable, "Mismatching lowest nondeterminism variable indices.");
  797. highestLocalNondeterminismVariable = std::max(highestLocalNondeterminismVariable, action.getHighestLocalNondeterminismVariable());
  798. }
  799. // Bring all actions to the same number of variables that encode the nondeterminism.
  800. for (auto& action : actions) {
  801. storm::dd::Bdd<Type> nondeterminismEncodingBdd = this->variables.manager->getBddOne();
  802. for (uint_fast64_t i = action.getHighestLocalNondeterminismVariable(); i < highestLocalNondeterminismVariable; ++i) {
  803. nondeterminismEncodingBdd &= this->variables.manager->getEncoding(this->variables.localNondeterminismVariables[i], 0);
  804. }
  805. storm::dd::Add<Type, ValueType> nondeterminismEncoding = nondeterminismEncodingBdd.template toAdd<ValueType>();
  806. action.transitions *= nondeterminismEncoding;
  807. for (auto& variableFragment : action.variableToWritingFragment) {
  808. variableFragment.second &= nondeterminismEncodingBdd;
  809. }
  810. for (auto& transientAssignment : action.transientEdgeAssignments) {
  811. transientAssignment.second *= nondeterminismEncoding;
  812. }
  813. }
  814. uint64_t numberOfLocalNondeterminismVariables = static_cast<uint64_t>(std::ceil(std::log2(actions.size())));
  815. storm::dd::Bdd<Type> guard = this->variables.manager->getBddZero();
  816. storm::dd::Add<Type, ValueType> transitions = this->variables.manager->template getAddZero<ValueType>();
  817. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  818. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> variableToWritingFragment;
  819. storm::dd::Bdd<Type> illegalFragment = this->variables.manager->getBddZero();
  820. for (uint64_t actionIndex = 0; actionIndex < actions.size(); ++actionIndex) {
  821. ActionDd& action = actions[actionIndex];
  822. guard |= action.guard.toBdd();
  823. storm::dd::Add<Type, ValueType> nondeterminismEncoding = encodeIndex(actionIndex, highestLocalNondeterminismVariable, numberOfLocalNondeterminismVariables, this->variables);
  824. transitions += nondeterminismEncoding * action.transitions;
  825. joinTransientAssignmentMaps(transientEdgeAssignments, action.transientEdgeAssignments);
  826. storm::dd::Bdd<Type> nondeterminismEncodingBdd = nondeterminismEncoding.toBdd();
  827. for (auto& entry : action.variableToWritingFragment) {
  828. entry.second &= nondeterminismEncodingBdd;
  829. }
  830. addToVariableWritingFragmentMap(variableToWritingFragment, action.variableToWritingFragment);
  831. illegalFragment |= action.illegalFragment;
  832. }
  833. return ActionDd(guard.template toAdd<ValueType>(), transitions, transientEdgeAssignments, std::make_pair(lowestLocalNondeterminismVariable, highestLocalNondeterminismVariable + numberOfLocalNondeterminismVariables), variableToWritingFragment, illegalFragment);
  834. } else {
  835. STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Illegal model type.");
  836. }
  837. }
  838. void performTransientAssignments(storm::jani::detail::ConstAssignments const& transientAssignments, std::function<void (storm::jani::Assignment const&)> const& callback) {
  839. auto transientVariableIt = this->transientVariables.begin();
  840. auto transientVariableIte = this->transientVariables.end();
  841. for (auto const& assignment : transientAssignments) {
  842. while (transientVariableIt != transientVariableIte && *transientVariableIt < assignment.getExpressionVariable()) {
  843. ++transientVariableIt;
  844. }
  845. if (transientVariableIt == transientVariableIte) {
  846. break;
  847. }
  848. if (*transientVariableIt == assignment.getExpressionVariable()) {
  849. callback(assignment);
  850. ++transientVariableIt;
  851. }
  852. }
  853. }
  854. EdgeDd buildEdgeDd(storm::jani::Automaton const& automaton, storm::jani::Edge const& edge) {
  855. STORM_LOG_TRACE("Translating guard " << edge.getGuard());
  856. // We keep the guard and a "ranged" version seperate, because building the destinations tends to be
  857. // slower when the full range is applied.
  858. storm::dd::Add<Type, ValueType> guard = this->variables.rowExpressionAdapter->translateExpression(edge.getGuard());
  859. storm::dd::Add<Type, ValueType> rangedGuard = guard * this->variables.automatonToRangeMap.at(automaton.getName());
  860. STORM_LOG_WARN_COND(!rangedGuard.isZero(), "The guard '" << edge.getGuard() << "' is unsatisfiable.");
  861. if (!rangedGuard.isZero()) {
  862. // Create the DDs representing the individual updates.
  863. std::vector<EdgeDestinationDd<Type, ValueType>> destinationDds;
  864. for (storm::jani::EdgeDestination const& destination : edge.getDestinations()) {
  865. destinationDds.push_back(buildEdgeDestinationDd(automaton, destination, guard, this->variables));
  866. STORM_LOG_WARN_COND(!destinationDds.back().transitions.isZero(), "Destination does not have any effect.");
  867. }
  868. // Now that we have built the destinations, we always take the full guard.
  869. guard = rangedGuard;
  870. // Start by gathering all variables that were written in at least one destination.
  871. std::set<storm::expressions::Variable> globalVariablesInSomeDestination;
  872. // If the edge is not labeled with the silent action, we have to analyze which portion of the global
  873. // variables was written by any of the updates and make all update results equal w.r.t. this set. If
  874. // the edge is labeled with the silent action, we can already multiply the identities of all global variables.
  875. if (edge.getActionIndex() != storm::jani::Model::SILENT_ACTION_INDEX) {
  876. for (auto const& edgeDestinationDd : destinationDds) {
  877. globalVariablesInSomeDestination.insert(edgeDestinationDd.writtenGlobalVariables.begin(), edgeDestinationDd.writtenGlobalVariables.end());
  878. }
  879. } else {
  880. globalVariablesInSomeDestination = this->variables.allGlobalVariables;
  881. }
  882. // Then, multiply the missing identities.
  883. for (auto& destinationDd : destinationDds) {
  884. std::set<storm::expressions::Variable> missingIdentities;
  885. std::set_difference(globalVariablesInSomeDestination.begin(), globalVariablesInSomeDestination.end(), destinationDd.writtenGlobalVariables.begin(), destinationDd.writtenGlobalVariables.end(), std::inserter(missingIdentities, missingIdentities.begin()));
  886. for (auto const& variable : missingIdentities) {
  887. STORM_LOG_TRACE("Multiplying identity for variable " << variable.getName() << " to destination DD.");
  888. destinationDd.transitions *= this->variables.variableToIdentityMap.at(variable);
  889. }
  890. }
  891. // Now combine the destination DDs to the edge DD.
  892. storm::dd::Add<Type, ValueType> transitions = this->variables.manager->template getAddZero<ValueType>();
  893. for (auto const& destinationDd : destinationDds) {
  894. transitions += destinationDd.transitions;
  895. }
  896. // Add the source location and the guard.
  897. storm::dd::Add<Type, ValueType> sourceLocationAndGuard = this->variables.manager->getEncoding(this->variables.automatonToLocationDdVariableMap.at(automaton.getName()).first, edge.getSourceLocationIndex()).template toAdd<ValueType>() * guard;
  898. transitions *= sourceLocationAndGuard;
  899. // If we multiply the ranges of global variables, make sure everything stays within its bounds.
  900. if (!globalVariablesInSomeDestination.empty()) {
  901. transitions *= this->variables.globalVariableRanges;
  902. }
  903. // If the edge has a rate, we multiply it to the DD.
  904. bool isMarkovian = false;
  905. if (edge.hasRate()) {
  906. transitions *= this->variables.rowExpressionAdapter->translateExpression(edge.getRate());
  907. isMarkovian = true;
  908. }
  909. // Finally treat the transient assignments.
  910. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  911. if (!this->transientVariables.empty()) {
  912. performTransientAssignments(edge.getAssignments().getTransientAssignments(), [this, &transientEdgeAssignments, &guard, &sourceLocationAndGuard] (storm::jani::Assignment const& assignment) {
  913. transientEdgeAssignments[assignment.getExpressionVariable()] = sourceLocationAndGuard * this->variables.rowExpressionAdapter->translateExpression(assignment.getAssignedExpression());
  914. } );
  915. }
  916. return EdgeDd(isMarkovian, guard, guard * transitions, transientEdgeAssignments, globalVariablesInSomeDestination);
  917. } else {
  918. return EdgeDd(false, rangedGuard, rangedGuard, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>>(), std::set<storm::expressions::Variable>());
  919. }
  920. }
  921. EdgeDd combineMarkovianEdgesToSingleEdge(std::vector<EdgeDd> const& edgeDds) {
  922. storm::dd::Bdd<Type> guard = this->variables.manager->getBddZero();
  923. storm::dd::Add<Type, ValueType> transitions = this->variables.manager->template getAddZero<ValueType>();
  924. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  925. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> variableToWritingFragment;
  926. bool overlappingGuards = false;
  927. for (auto const& edge : edgeDds) {
  928. STORM_LOG_THROW(edge.isMarkovian, storm::exceptions::InvalidArgumentException, "Can only combine Markovian edges.");
  929. if (!overlappingGuards) {
  930. overlappingGuards |= !(guard && edge.guard.toBdd()).isZero();
  931. }
  932. guard |= edge.guard.toBdd();
  933. transitions += edge.transitions;
  934. variableToWritingFragment = joinVariableWritingFragmentMaps(variableToWritingFragment, edge.variableToWritingFragment);
  935. joinTransientAssignmentMaps(transientEdgeAssignments, edge.transientEdgeAssignments);
  936. }
  937. // Currently, we can only combine the transient edge assignments if there is no overlap of the guards of the edges.
  938. STORM_LOG_THROW(!overlappingGuards || transientEdgeAssignments.empty(), storm::exceptions::NotSupportedException, "Cannot have transient edge assignments when combining Markovian edges with overlapping guards.");
  939. return EdgeDd(true, guard.template toAdd<ValueType>(), transitions, transientEdgeAssignments, variableToWritingFragment);
  940. }
  941. ActionDd buildActionDdForActionIndex(storm::jani::Automaton const& automaton, uint64_t actionIndex, uint64_t localNondeterminismVariableOffset) {
  942. // Translate the individual edges.
  943. std::vector<EdgeDd> markovianEdges;
  944. std::vector<EdgeDd> nonMarkovianEdges;
  945. uint64_t numberOfEdges = 0;
  946. for (auto const& edge : automaton.getEdges()) {
  947. ++numberOfEdges;
  948. if (edge.getActionIndex() == actionIndex) {
  949. EdgeDd result = buildEdgeDd(automaton, edge);
  950. if (result.isMarkovian) {
  951. markovianEdges.push_back(result);
  952. } else {
  953. nonMarkovianEdges.push_back(result);
  954. }
  955. }
  956. }
  957. // Now combine the edges to a single action.
  958. if (numberOfEdges > 0) {
  959. storm::jani::ModelType modelType = this->model.getModelType();
  960. if (modelType == storm::jani::ModelType::DTMC) {
  961. STORM_LOG_THROW(markovianEdges.empty(), storm::exceptions::WrongFormatException, "Illegal Markovian edges in DTMC.");
  962. return combineEdgesToActionDeterministic(nonMarkovianEdges);
  963. } else if (modelType == storm::jani::ModelType::CTMC) {
  964. STORM_LOG_THROW(nonMarkovianEdges.empty(), storm::exceptions::WrongFormatException, "Illegal non-Markovian edges in CTMC.");
  965. return combineEdgesToActionDeterministic(markovianEdges);
  966. } else if (modelType == storm::jani::ModelType::MDP) {
  967. STORM_LOG_THROW(markovianEdges.empty(), storm::exceptions::WrongFormatException, "Illegal Markovian edges in MDP.");
  968. return combineEdgesToActionNondeterministic(nonMarkovianEdges, boost::none, localNondeterminismVariableOffset);
  969. } else if (modelType == storm::jani::ModelType::MA) {
  970. boost::optional<EdgeDd> markovianEdge = boost::none;
  971. if (markovianEdges.size() > 1) {
  972. markovianEdge = combineMarkovianEdgesToSingleEdge(markovianEdges);
  973. } else if (markovianEdges.size() == 1) {
  974. markovianEdge = markovianEdges.front();
  975. }
  976. return combineEdgesToActionNondeterministic(nonMarkovianEdges, markovianEdge, localNondeterminismVariableOffset);
  977. } else {
  978. STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Cannot translate model of this type.");
  979. }
  980. } else {
  981. return ActionDd(this->variables.manager->template getAddZero<ValueType>(), this->variables.manager->template getAddZero<ValueType>(), {}, std::make_pair<uint64_t, uint64_t>(0, 0), {}, this->variables.manager->getBddZero());
  982. }
  983. }
  984. void addToTransientAssignmentMap(std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>>& transientAssignments, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& assignmentsToAdd) {
  985. for (auto const& entry : assignmentsToAdd) {
  986. auto it = transientAssignments.find(entry.first);
  987. if (it != transientAssignments.end()) {
  988. it->second += entry.second;
  989. } else {
  990. transientAssignments[entry.first] = entry.second;
  991. }
  992. }
  993. }
  994. void addToTransientAssignmentMap(std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>>& transientAssignments, storm::expressions::Variable const& variable, storm::dd::Add<Type, ValueType> const& assignmentToAdd) {
  995. auto it = transientAssignments.find(variable);
  996. if (it != transientAssignments.end()) {
  997. it->second += assignmentToAdd;
  998. } else {
  999. transientAssignments[variable] = assignmentToAdd;
  1000. }
  1001. }
  1002. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> joinTransientAssignmentMaps(std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientAssignments1, std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> const& transientAssignments2) {
  1003. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> result = transientAssignments1;
  1004. for (auto const& entry : transientAssignments2) {
  1005. auto resultIt = result.find(entry.first);
  1006. if (resultIt != result.end()) {
  1007. resultIt->second += entry.second;
  1008. } else {
  1009. result[entry.first] = entry.second;
  1010. }
  1011. }
  1012. return result;
  1013. }
  1014. ActionDd combineEdgesToActionDeterministic(std::vector<EdgeDd> const& edgeDds) {
  1015. storm::dd::Bdd<Type> allGuards = this->variables.manager->getBddZero();
  1016. storm::dd::Add<Type, ValueType> allTransitions = this->variables.manager->template getAddZero<ValueType>();
  1017. storm::dd::Bdd<Type> temporary;
  1018. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> globalVariableToWritingFragment;
  1019. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  1020. bool overlappingGuards = false;
  1021. for (auto const& edgeDd : edgeDds) {
  1022. STORM_LOG_THROW((this->model.getModelType() == storm::jani::ModelType::CTMC) == edgeDd.isMarkovian, storm::exceptions::WrongFormatException, "Unexpected non-Markovian edge in CTMC.");
  1023. // Check for overlapping guards.
  1024. storm::dd::Bdd<Type> guardBdd = edgeDd.guard.toBdd();
  1025. overlappingGuards = !(guardBdd && allGuards).isZero();
  1026. // Issue a warning if there are overlapping guards in a DTMC.
  1027. STORM_LOG_WARN_COND(!overlappingGuards || this->model.getModelType() == storm::jani::ModelType::CTMC, "Guard of an edge in a DTMC overlaps with previous guards.");
  1028. // Add the elements of the current edge to the global ones.
  1029. allGuards |= guardBdd;
  1030. allTransitions += edgeDd.transitions;
  1031. // Add the transient variable assignments to the resulting one. This transformation is illegal for
  1032. // CTMCs for which there is some overlap in edges that have some transient assignment (this needs to
  1033. // be checked later).
  1034. addToTransientAssignmentMap(transientEdgeAssignments, edgeDd.transientEdgeAssignments);
  1035. // Keep track of the fragment that is writing global variables.
  1036. globalVariableToWritingFragment = joinVariableWritingFragmentMaps(globalVariableToWritingFragment, edgeDd.variableToWritingFragment);
  1037. }
  1038. STORM_LOG_THROW(this->model.getModelType() == storm::jani::ModelType::DTMC || !overlappingGuards || transientEdgeAssignments.empty(), storm::exceptions::NotSupportedException, "Cannot have transient edge assignments when combining Markovian edges with overlapping guards.");
  1039. return ActionDd(allGuards.template toAdd<ValueType>(), allTransitions, transientEdgeAssignments, std::make_pair<uint64_t, uint64_t>(0, 0), globalVariableToWritingFragment, this->variables.manager->getBddZero());
  1040. }
  1041. void addToVariableWritingFragmentMap(std::map<storm::expressions::Variable, storm::dd::Bdd<Type>>& globalVariableToWritingFragment, storm::expressions::Variable const& variable, storm::dd::Bdd<Type> const& partToAdd) const {
  1042. auto it = globalVariableToWritingFragment.find(variable);
  1043. if (it != globalVariableToWritingFragment.end()) {
  1044. it->second |= partToAdd;
  1045. } else {
  1046. globalVariableToWritingFragment.emplace(variable, partToAdd);
  1047. }
  1048. }
  1049. void addToVariableWritingFragmentMap(std::map<storm::expressions::Variable, storm::dd::Bdd<Type>>& globalVariableToWritingFragment, std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> const& partToAdd) const {
  1050. for (auto const& entry : partToAdd) {
  1051. addToVariableWritingFragmentMap(globalVariableToWritingFragment, entry.first, entry.second);
  1052. }
  1053. }
  1054. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> joinVariableWritingFragmentMaps(std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> const& globalVariableToWritingFragment1, std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> const& globalVariableToWritingFragment2) {
  1055. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> result = globalVariableToWritingFragment1;
  1056. for (auto const& entry : globalVariableToWritingFragment2) {
  1057. auto resultIt = result.find(entry.first);
  1058. if (resultIt != result.end()) {
  1059. resultIt->second |= entry.second;
  1060. } else {
  1061. result[entry.first] = entry.second;
  1062. }
  1063. }
  1064. return result;
  1065. }
  1066. ActionDd combineEdgesBySummation(storm::dd::Add<Type, ValueType> const& guard, std::vector<EdgeDd> const& edges, boost::optional<EdgeDd> const& markovianEdge) {
  1067. bool addMarkovianFlag = this->model.getModelType() == storm::jani::ModelType::MA;
  1068. STORM_LOG_ASSERT(addMarkovianFlag || !markovianEdge, "Illegally adding Markovian edge without marker.");
  1069. storm::dd::Add<Type, ValueType> transitions = this->variables.manager->template getAddZero<ValueType>();
  1070. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> globalVariableToWritingFragment;
  1071. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  1072. storm::dd::Bdd<Type> flagBdd = addMarkovianFlag ? !this->variables.markovMarker : this->variables.manager->getBddOne();
  1073. storm::dd::Add<Type, ValueType> flag = flagBdd.template toAdd<ValueType>();
  1074. for (auto const& edge : edges) {
  1075. transitions += addMarkovianFlag ? flag * edge.transitions : edge.transitions;
  1076. for (auto const& assignment : edge.transientEdgeAssignments) {
  1077. addToTransientAssignmentMap(transientEdgeAssignments, assignment.first, addMarkovianFlag ? flag * assignment.second : assignment.second);
  1078. }
  1079. for (auto const& variableFragment : edge.variableToWritingFragment) {
  1080. addToVariableWritingFragmentMap(globalVariableToWritingFragment, variableFragment.first, addMarkovianFlag ? flagBdd && variableFragment.second : variableFragment.second);
  1081. }
  1082. }
  1083. // Add the Markovian edge (if any).
  1084. if (markovianEdge) {
  1085. flagBdd = addMarkovianFlag ? !this->variables.markovMarker : this->variables.manager->getBddOne();
  1086. flag = flagBdd.template toAdd<ValueType>();
  1087. EdgeDd const& edge = markovianEdge.get();
  1088. transitions += flag * edge.transitions;
  1089. for (auto const& assignment : edge.transientEdgeAssignments) {
  1090. addToTransientAssignmentMap(transientEdgeAssignments, assignment.first, addMarkovianFlag ? flag * assignment.second : assignment.second);
  1091. }
  1092. for (auto const& variableFragment : edge.variableToWritingFragment) {
  1093. addToVariableWritingFragmentMap(globalVariableToWritingFragment, variableFragment.first, addMarkovianFlag ? flagBdd && variableFragment.second : variableFragment.second);
  1094. }
  1095. }
  1096. return ActionDd(guard, transitions, transientEdgeAssignments, std::make_pair<uint64_t, uint64_t>(0, 0), globalVariableToWritingFragment, this->variables.manager->getBddZero());
  1097. }
  1098. ActionDd combineEdgesToActionNondeterministic(std::vector<EdgeDd> const& nonMarkovianEdges, boost::optional<EdgeDd> const& markovianEdge, uint64_t localNondeterminismVariableOffset) {
  1099. // Sum all guards, so we can read off the maximal number of nondeterministic choices in any given state.
  1100. storm::dd::Bdd<Type> allGuards = this->variables.manager->getBddZero();
  1101. storm::dd::Add<Type, ValueType> sumOfGuards = this->variables.manager->template getAddZero<ValueType>();
  1102. for (auto const& edge : nonMarkovianEdges) {
  1103. STORM_LOG_ASSERT(!edge.isMarkovian, "Unexpected Markovian edge.");
  1104. sumOfGuards += edge.guard;
  1105. allGuards |= edge.guard.toBdd();
  1106. }
  1107. uint_fast64_t maxChoices = static_cast<uint_fast64_t>(sumOfGuards.getMax());
  1108. STORM_LOG_TRACE("Found " << maxChoices << " non-Markovian local choices.");
  1109. // Depending on the maximal number of nondeterminstic choices, we need to use some variables to encode the nondeterminism.
  1110. if (maxChoices <= 1) {
  1111. return combineEdgesBySummation(allGuards.template toAdd<ValueType>(), nonMarkovianEdges, markovianEdge);
  1112. } else {
  1113. // Calculate number of required variables to encode the nondeterminism.
  1114. uint_fast64_t numberOfBinaryVariables = static_cast<uint_fast64_t>(std::ceil(storm::utility::math::log2(maxChoices)));
  1115. storm::dd::Add<Type, ValueType> allEdges = this->variables.manager->template getAddZero<ValueType>();
  1116. std::map<storm::expressions::Variable, storm::dd::Bdd<Type>> globalVariableToWritingFragment;
  1117. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientAssignments;
  1118. storm::dd::Bdd<Type> equalsNumberOfChoicesDd;
  1119. std::vector<storm::dd::Add<Type, ValueType>> choiceDds(maxChoices, this->variables.manager->template getAddZero<ValueType>());
  1120. std::vector<storm::dd::Bdd<Type>> remainingDds(maxChoices, this->variables.manager->getBddZero());
  1121. std::vector<std::pair<storm::dd::Bdd<Type>, storm::dd::Add<Type, ValueType>>> indicesEncodedWithLocalNondeterminismVariables;
  1122. for (uint64_t j = 0; j < maxChoices; ++j) {
  1123. storm::dd::Add<Type, ValueType> indexEncoding = encodeIndex(j, localNondeterminismVariableOffset, numberOfBinaryVariables, this->variables);
  1124. indicesEncodedWithLocalNondeterminismVariables.push_back(std::make_pair(indexEncoding.toBdd(), indexEncoding));
  1125. }
  1126. for (uint_fast64_t currentChoices = 1; currentChoices <= maxChoices; ++currentChoices) {
  1127. // Determine the set of states with exactly currentChoices choices.
  1128. equalsNumberOfChoicesDd = sumOfGuards.equals(this->variables.manager->getConstant(static_cast<double>(currentChoices)));
  1129. // If there is no such state, continue with the next possible number of choices.
  1130. if (equalsNumberOfChoicesDd.isZero()) {
  1131. continue;
  1132. }
  1133. // Reset the previously used intermediate storage.
  1134. for (uint_fast64_t j = 0; j < currentChoices; ++j) {
  1135. choiceDds[j] = this->variables.manager->template getAddZero<ValueType>();
  1136. remainingDds[j] = equalsNumberOfChoicesDd;
  1137. }
  1138. for (std::size_t j = 0; j < nonMarkovianEdges.size(); ++j) {
  1139. EdgeDd const& currentEdge = nonMarkovianEdges[j];
  1140. // Check if edge guard overlaps with equalsNumberOfChoicesDd. That is, there are states with exactly currentChoices
  1141. // choices such that one outgoing choice is given by the j-th edge.
  1142. storm::dd::Bdd<Type> guardChoicesIntersection = currentEdge.guard.toBdd() && equalsNumberOfChoicesDd;
  1143. // If there is no such state, continue with the next command.
  1144. if (guardChoicesIntersection.isZero()) {
  1145. continue;
  1146. }
  1147. // Split the currentChoices nondeterministic choices.
  1148. for (uint_fast64_t k = 0; k < currentChoices; ++k) {
  1149. // Calculate the overlapping part of command guard and the remaining DD.
  1150. storm::dd::Bdd<Type> remainingGuardChoicesIntersection = guardChoicesIntersection && remainingDds[k];
  1151. // Check if we can add some overlapping parts to the current index.
  1152. if (!remainingGuardChoicesIntersection.isZero()) {
  1153. // Remove overlapping parts from the remaining DD.
  1154. remainingDds[k] = remainingDds[k] && !remainingGuardChoicesIntersection;
  1155. // Combine the overlapping part of the guard with command updates and add it to the resulting DD.
  1156. choiceDds[k] += remainingGuardChoicesIntersection.template toAdd<ValueType>() * currentEdge.transitions;
  1157. // Keep track of the fragment of transient assignments.
  1158. for (auto const& transientAssignment : currentEdge.transientEdgeAssignments) {
  1159. addToTransientAssignmentMap(transientAssignments, transientAssignment.first, remainingGuardChoicesIntersection.template toAdd<ValueType>() * transientAssignment.second * indicesEncodedWithLocalNondeterminismVariables[k].first.template toAdd<ValueType>());
  1160. }
  1161. // Keep track of the written global variables of the fragment.
  1162. for (auto const& variableFragment : currentEdge.variableToWritingFragment) {
  1163. addToVariableWritingFragmentMap(globalVariableToWritingFragment, variableFragment.first, remainingGuardChoicesIntersection && variableFragment.second && indicesEncodedWithLocalNondeterminismVariables[k].first);
  1164. }
  1165. }
  1166. // Remove overlapping parts from the command guard DD
  1167. guardChoicesIntersection = guardChoicesIntersection && !remainingGuardChoicesIntersection;
  1168. // If the guard DD has become equivalent to false, we can stop here.
  1169. if (guardChoicesIntersection.isZero()) {
  1170. break;
  1171. }
  1172. }
  1173. }
  1174. // Add the meta variables that encode the nondeterminisim to the different choices.
  1175. for (uint_fast64_t j = 0; j < currentChoices; ++j) {
  1176. allEdges += indicesEncodedWithLocalNondeterminismVariables[j].second * choiceDds[j];
  1177. }
  1178. // Delete currentChoices out of overlapping DD
  1179. sumOfGuards = sumOfGuards * (!equalsNumberOfChoicesDd).template toAdd<ValueType>();
  1180. }
  1181. // Extend the transitions with the appropriate flag if needed.
  1182. bool addMarkovianFlag = this->model.getModelType() == storm::jani::ModelType::MA;
  1183. STORM_LOG_ASSERT(addMarkovianFlag || !markovianEdge, "Illegally adding Markovian edge without marker.");
  1184. if (addMarkovianFlag) {
  1185. storm::dd::Bdd<Type> flagBdd = !this->variables.markovMarker;
  1186. storm::dd::Add<Type, ValueType> flag = flagBdd.template toAdd<ValueType>();
  1187. allEdges *= flag;
  1188. for (auto& assignment : transientAssignments) {
  1189. assignment.second *= flag;
  1190. }
  1191. for (auto& writingFragment : globalVariableToWritingFragment) {
  1192. writingFragment.second &= flagBdd;
  1193. }
  1194. }
  1195. // Add Markovian edge (if there is any).
  1196. if (markovianEdge) {
  1197. storm::dd::Bdd<Type> flagBdd = this->variables.markovMarker;
  1198. storm::dd::Add<Type, ValueType> flag = flagBdd.template toAdd<ValueType>();
  1199. EdgeDd const& edge = markovianEdge.get();
  1200. allEdges += flag * edge.transitions;
  1201. for (auto const& assignment : edge.transientEdgeAssignments) {
  1202. addToTransientAssignmentMap(transientAssignments, assignment.first, flag * assignment.second);
  1203. }
  1204. for (auto const& variableFragment : edge.variableToWritingFragment) {
  1205. addToVariableWritingFragmentMap(globalVariableToWritingFragment, variableFragment.first, flagBdd && variableFragment.second);
  1206. }
  1207. }
  1208. return ActionDd(allGuards.template toAdd<ValueType>(), allEdges, transientAssignments, std::make_pair(localNondeterminismVariableOffset, localNondeterminismVariableOffset + numberOfBinaryVariables), globalVariableToWritingFragment, this->variables.manager->getBddZero());
  1209. }
  1210. }
  1211. AutomatonDd buildAutomatonDd(std::string const& automatonName, std::map<uint_fast64_t, uint_fast64_t> const& actionIndexToLocalNondeterminismVariableOffset, std::set<uint64_t> const& inputEnabledActionIndices) {
  1212. AutomatonDd result(this->variables.automatonToIdentityMap.at(automatonName));
  1213. storm::jani::Automaton const& automaton = this->model.getAutomaton(automatonName);
  1214. for (auto const& action : this->model.getActions()) {
  1215. uint64_t actionIndex = this->model.getActionIndex(action.getName());
  1216. if (!automaton.hasEdgeLabeledWithActionIndex(actionIndex)) {
  1217. continue;
  1218. }
  1219. ActionDd actionDd = buildActionDdForActionIndex(automaton, actionIndex, actionIndexToLocalNondeterminismVariableOffset.at(actionIndex));
  1220. if (inputEnabledActionIndices.find(actionIndex) != inputEnabledActionIndices.end()) {
  1221. actionDd.setIsInputEnabled();
  1222. }
  1223. result.actionIndexToAction[actionIndex] = actionDd;
  1224. result.setLowestLocalNondeterminismVariable(std::max(result.getLowestLocalNondeterminismVariable(), actionDd.getLowestLocalNondeterminismVariable()));
  1225. result.setHighestLocalNondeterminismVariable(std::max(result.getHighestLocalNondeterminismVariable(), actionDd.getHighestLocalNondeterminismVariable()));
  1226. }
  1227. for (uint64_t locationIndex = 0; locationIndex < automaton.getNumberOfLocations(); ++locationIndex) {
  1228. auto const& location = automaton.getLocation(locationIndex);
  1229. performTransientAssignments(location.getAssignments().getTransientAssignments(), [this,&automatonName,locationIndex,&result] (storm::jani::Assignment const& assignment) {
  1230. storm::dd::Add<Type, ValueType> assignedValues = this->variables.manager->getEncoding(this->variables.automatonToLocationDdVariableMap.at(automatonName).first, locationIndex).template toAdd<ValueType>() * this->variables.rowExpressionAdapter->translateExpression(assignment.getAssignedExpression());
  1231. auto it = result.transientLocationAssignments.find(assignment.getExpressionVariable());
  1232. if (it != result.transientLocationAssignments.end()) {
  1233. it->second += assignedValues;
  1234. } else {
  1235. result.transientLocationAssignments[assignment.getExpressionVariable()] = assignedValues;
  1236. }
  1237. });
  1238. }
  1239. return result;
  1240. }
  1241. void addMissingGlobalVariableIdentities(ActionDd& action) {
  1242. // Build a DD that we can multiply to the transitions and adds all missing global variable identities that way.
  1243. storm::dd::Add<Type, ValueType> missingIdentities = this->variables.manager->template getAddOne<ValueType>();
  1244. for (auto const& variable : this->variables.allGlobalVariables) {
  1245. auto it = action.variableToWritingFragment.find(variable);
  1246. if (it != action.variableToWritingFragment.end()) {
  1247. missingIdentities *= (it->second).ite(this->variables.manager->template getAddOne<ValueType>(), this->variables.variableToIdentityMap.at(variable));
  1248. } else {
  1249. missingIdentities *= this->variables.variableToIdentityMap.at(variable);
  1250. }
  1251. }
  1252. action.transitions *= missingIdentities;
  1253. }
  1254. ComposerResult<Type, ValueType> buildSystemFromAutomaton(AutomatonDd& automaton) {
  1255. // If the model is an MDP, we need to encode the nondeterminism using additional variables.
  1256. if (this->model.getModelType() == storm::jani::ModelType::MDP) {
  1257. storm::dd::Add<Type, ValueType> result = this->variables.manager->template getAddZero<ValueType>();
  1258. storm::dd::Bdd<Type> illegalFragment = this->variables.manager->getBddZero();
  1259. // First, determine the highest number of nondeterminism variables that is used in any action and make
  1260. // all actions use the same amout of nondeterminism variables.
  1261. uint64_t numberOfUsedNondeterminismVariables = automaton.getHighestLocalNondeterminismVariable();
  1262. // Add missing global variable identities, action and nondeterminism encodings.
  1263. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  1264. for (auto& action : automaton.actionIndexToAction) {
  1265. illegalFragment |= action.second.illegalFragment;
  1266. addMissingGlobalVariableIdentities(action.second);
  1267. storm::dd::Add<Type, ValueType> actionEncoding = encodeAction(action.first != storm::jani::Model::SILENT_ACTION_INDEX ? boost::optional<uint64_t>(action.first) : boost::none, this->variables);
  1268. storm::dd::Add<Type, ValueType> missingNondeterminismEncoding = encodeIndex(0, action.second.getHighestLocalNondeterminismVariable(), numberOfUsedNondeterminismVariables - action.second.getHighestLocalNondeterminismVariable(), this->variables);
  1269. storm::dd::Add<Type, ValueType> extendedTransitions = actionEncoding * missingNondeterminismEncoding * action.second.transitions;
  1270. for (auto const& transientAssignment : action.second.transientEdgeAssignments) {
  1271. addToTransientAssignmentMap(transientEdgeAssignments, transientAssignment.first, actionEncoding * missingNondeterminismEncoding * transientAssignment.second);
  1272. }
  1273. result += extendedTransitions;
  1274. }
  1275. return ComposerResult<Type, ValueType>(result, automaton.transientLocationAssignments, transientEdgeAssignments, illegalFragment, numberOfUsedNondeterminismVariables);
  1276. } else if (this->model.getModelType() == storm::jani::ModelType::DTMC || this->model.getModelType() == storm::jani::ModelType::CTMC) {
  1277. // Simply add all actions, but make sure to include the missing global variable identities.
  1278. storm::dd::Add<Type, ValueType> result = this->variables.manager->template getAddZero<ValueType>();
  1279. storm::dd::Bdd<Type> illegalFragment = this->variables.manager->getBddZero();
  1280. std::map<storm::expressions::Variable, storm::dd::Add<Type, ValueType>> transientEdgeAssignments;
  1281. for (auto& action : automaton.actionIndexToAction) {
  1282. illegalFragment |= action.second.illegalFragment;
  1283. addMissingGlobalVariableIdentities(action.second);
  1284. addToTransientAssignmentMap(transientEdgeAssignments, action.second.transientEdgeAssignments);
  1285. result += action.second.transitions;
  1286. }
  1287. return ComposerResult<Type, ValueType>(result, automaton.transientLocationAssignments, transientEdgeAssignments, illegalFragment, 0);
  1288. } else {
  1289. STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Illegal model type.");
  1290. }
  1291. }
  1292. };
  1293. template <storm::dd::DdType Type, typename ValueType>
  1294. struct ModelComponents {
  1295. storm::dd::Bdd<Type> reachableStates;
  1296. storm::dd::Bdd<Type> initialStates;
  1297. storm::dd::Bdd<Type> deadlockStates;
  1298. storm::dd::Add<Type, ValueType> transitionMatrix;
  1299. std::unordered_map<std::string, storm::models::symbolic::StandardRewardModel<Type, ValueType>> rewardModels;
  1300. std::map<std::string, storm::expressions::Expression> labelToExpressionMap;
  1301. };
  1302. template <storm::dd::DdType Type, typename ValueType>
  1303. std::shared_ptr<storm::models::symbolic::Model<Type, ValueType>> createModel(storm::jani::ModelType const& modelType, CompositionVariables<Type, ValueType> const& variables, ModelComponents<Type, ValueType> const& modelComponents) {
  1304. if (modelType == storm::jani::ModelType::DTMC) {
  1305. return std::shared_ptr<storm::models::symbolic::Model<Type, ValueType>>(new storm::models::symbolic::Dtmc<Type, ValueType>(variables.manager, modelComponents.reachableStates, modelComponents.initialStates, modelComponents.deadlockStates, modelComponents.transitionMatrix, variables.rowMetaVariables, variables.rowExpressionAdapter, variables.columnMetaVariables, variables.columnExpressionAdapter, variables.rowColumnMetaVariablePairs, modelComponents.labelToExpressionMap, modelComponents.rewardModels));
  1306. } else if (modelType == storm::jani::ModelType::CTMC) {
  1307. return std::shared_ptr<storm::models::symbolic::Model<Type, ValueType>>(new storm::models::symbolic::Ctmc<Type, ValueType>(variables.manager, modelComponents.reachableStates, modelComponents.initialStates, modelComponents.deadlockStates, modelComponents.transitionMatrix, variables.rowMetaVariables, variables.rowExpressionAdapter, variables.columnMetaVariables, variables.columnExpressionAdapter, variables.rowColumnMetaVariablePairs, modelComponents.labelToExpressionMap, modelComponents.rewardModels));
  1308. } else if (modelType == storm::jani::ModelType::MDP) {
  1309. return std::shared_ptr<storm::models::symbolic::Model<Type, ValueType>>(new storm::models::symbolic::Mdp<Type, ValueType>(variables.manager, modelComponents.reachableStates, modelComponents.initialStates, modelComponents.deadlockStates, modelComponents.transitionMatrix, variables.rowMetaVariables, variables.rowExpressionAdapter, variables.columnMetaVariables, variables.columnExpressionAdapter, variables.rowColumnMetaVariablePairs, variables.allNondeterminismVariables, modelComponents.labelToExpressionMap, modelComponents.rewardModels));
  1310. } else {
  1311. STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Invalid model type.");
  1312. }
  1313. }
  1314. template <storm::dd::DdType Type, typename ValueType>
  1315. void postprocessVariables(storm::jani::ModelType const& modelType, ComposerResult<Type, ValueType>& system, CompositionVariables<Type, ValueType>& variables) {
  1316. // Add all action/row/column variables to the DD. If we omitted multiplying edges in the construction, this will
  1317. // introduce the variables so they can later be abstracted without raising an error.
  1318. system.transitions.addMetaVariables(variables.rowMetaVariables);
  1319. system.transitions.addMetaVariables(variables.columnMetaVariables);
  1320. // If the model is an MDP, we also add all action variables.
  1321. if (modelType == storm::jani::ModelType::MDP) {
  1322. for (auto const& actionVariablePair : variables.actionVariablesMap) {
  1323. system.transitions.addMetaVariable(actionVariablePair.second);
  1324. }
  1325. }
  1326. // Get rid of the local nondeterminism variables that were not used.
  1327. for (uint64_t index = system.numberOfNondeterminismVariables; index < variables.localNondeterminismVariables.size(); ++index) {
  1328. variables.allNondeterminismVariables.erase(variables.localNondeterminismVariables[index]);
  1329. }
  1330. variables.localNondeterminismVariables.resize(system.numberOfNondeterminismVariables);
  1331. }
  1332. template <storm::dd::DdType Type, typename ValueType>
  1333. storm::dd::Bdd<Type> postprocessSystem(storm::jani::Model const& model, ComposerResult<Type, ValueType>& system, CompositionVariables<Type, ValueType> const& variables, typename DdJaniModelBuilder<Type, ValueType>::Options const& options) {
  1334. // For DTMCs, we normalize each row to 1 (to account for non-determinism).
  1335. if (model.getModelType() == storm::jani::ModelType::DTMC) {
  1336. storm::dd::Add<Type, ValueType> stateToNumberOfChoices = system.transitions.sumAbstract(variables.columnMetaVariables);
  1337. system.transitions = system.transitions / stateToNumberOfChoices;
  1338. // Scale all state-action rewards.
  1339. for (auto& entry : system.transientEdgeAssignments) {
  1340. entry.second = entry.second / stateToNumberOfChoices;
  1341. }
  1342. }
  1343. // If we were asked to treat some states as terminal states, we cut away their transitions now.
  1344. if (options.terminalStates || options.negatedTerminalStates) {
  1345. std::map<storm::expressions::Variable, storm::expressions::Expression> constantsSubstitution = model.getConstantsSubstitution();
  1346. storm::dd::Bdd<Type> terminalStatesBdd = variables.manager->getBddZero();
  1347. if (options.terminalStates) {
  1348. storm::expressions::Expression terminalExpression = options.terminalStates.get().substitute(constantsSubstitution);
  1349. STORM_LOG_TRACE("Making the states satisfying " << terminalExpression << " terminal.");
  1350. terminalStatesBdd = variables.rowExpressionAdapter->translateExpression(terminalExpression).toBdd();
  1351. }
  1352. if (options.negatedTerminalStates) {
  1353. storm::expressions::Expression negatedTerminalExpression = options.negatedTerminalStates.get().substitute(constantsSubstitution);
  1354. STORM_LOG_TRACE("Making the states *not* satisfying " << negatedTerminalExpression << " terminal.");
  1355. terminalStatesBdd |= !variables.rowExpressionAdapter->translateExpression(negatedTerminalExpression).toBdd();
  1356. }
  1357. system.transitions *= (!terminalStatesBdd).template toAdd<ValueType>();
  1358. return terminalStatesBdd;
  1359. }
  1360. return variables.manager->getBddZero();
  1361. }
  1362. template <storm::dd::DdType Type, typename ValueType>
  1363. storm::dd::Bdd<Type> computeInitialStates(storm::jani::Model const& model, CompositionVariables<Type, ValueType> const& variables) {
  1364. std::vector<std::reference_wrapper<storm::jani::Automaton const>> allAutomata;
  1365. for (auto const& automaton : model.getAutomata()) {
  1366. allAutomata.push_back(automaton);
  1367. }
  1368. storm::dd::Bdd<Type> initialStates = variables.rowExpressionAdapter->translateExpression(model.getInitialStatesExpression(allAutomata)).toBdd();
  1369. for (auto const& automaton : model.getAutomata()) {
  1370. storm::dd::Bdd<Type> initialLocationIndices = variables.manager->getBddZero();
  1371. for (auto const& locationIndex : automaton.getInitialLocationIndices()) {
  1372. initialLocationIndices |= variables.manager->getEncoding(variables.automatonToLocationDdVariableMap.at(automaton.getName()).first, locationIndex);
  1373. }
  1374. initialStates &= initialLocationIndices;
  1375. }
  1376. for (auto const& metaVariable : variables.rowMetaVariables) {
  1377. initialStates &= variables.variableToRangeMap.at(metaVariable);
  1378. }
  1379. return initialStates;
  1380. }
  1381. template <storm::dd::DdType Type, typename ValueType>
  1382. storm::dd::Bdd<Type> fixDeadlocks(storm::jani::ModelType const& modelType, storm::dd::Add<Type, ValueType>& transitionMatrix, storm::dd::Bdd<Type> const& transitionMatrixBdd, storm::dd::Bdd<Type> const& reachableStates, CompositionVariables<Type, ValueType> const& variables) {
  1383. // Detect deadlocks and 1) fix them if requested 2) throw an error otherwise.
  1384. storm::dd::Bdd<Type> statesWithTransition = transitionMatrixBdd.existsAbstract(variables.columnMetaVariables);
  1385. storm::dd::Bdd<Type> deadlockStates = reachableStates && !statesWithTransition;
  1386. if (!deadlockStates.isZero()) {
  1387. // If we need to fix deadlocks, we do so now.
  1388. if (!storm::settings::getModule<storm::settings::modules::CoreSettings>().isDontFixDeadlocksSet()) {
  1389. STORM_LOG_INFO("Fixing deadlocks in " << deadlockStates.getNonZeroCount() << " states. The first three of these states are: ");
  1390. storm::dd::Add<Type, ValueType> deadlockStatesAdd = deadlockStates.template toAdd<ValueType>();
  1391. uint_fast64_t count = 0;
  1392. for (auto it = deadlockStatesAdd.begin(), ite = deadlockStatesAdd.end(); it != ite && count < 3; ++it, ++count) {
  1393. STORM_LOG_INFO((*it).first.toPrettyString(variables.rowMetaVariables) << std::endl);
  1394. }
  1395. // Create a global identity DD.
  1396. storm::dd::Add<Type, ValueType> globalIdentity = variables.manager->template getAddOne<ValueType>();
  1397. for (auto const& identity : variables.automatonToIdentityMap) {
  1398. globalIdentity *= identity.second;
  1399. }
  1400. for (auto const& variable : variables.allGlobalVariables) {
  1401. globalIdentity *= variables.variableToIdentityMap.at(variable);
  1402. }
  1403. if (modelType == storm::jani::ModelType::DTMC || modelType == storm::jani::ModelType::CTMC) {
  1404. // For DTMCs, we can simply add the identity of the global module for all deadlock states.
  1405. transitionMatrix += deadlockStatesAdd * globalIdentity;
  1406. } else if (modelType == storm::jani::ModelType::MDP) {
  1407. // For MDPs, however, we need to select an action associated with the self-loop, if we do not
  1408. // want to attach a lot of self-loops to the deadlock states.
  1409. storm::dd::Add<Type, ValueType> action = variables.manager->template getAddOne<ValueType>();
  1410. for (auto const& variable : variables.actionVariablesMap) {
  1411. action *= variables.manager->template getIdentity<ValueType>(variable.second);
  1412. }
  1413. for (auto const& variable : variables.localNondeterminismVariables) {
  1414. action *= variables.manager->template getIdentity<ValueType>(variable);
  1415. }
  1416. transitionMatrix += deadlockStatesAdd * globalIdentity * action;
  1417. }
  1418. } else {
  1419. 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.");
  1420. }
  1421. }
  1422. return deadlockStates;
  1423. }
  1424. template <storm::dd::DdType Type, typename ValueType>
  1425. std::vector<storm::expressions::Variable> selectRewardVariables(storm::jani::Model const& model, typename DdJaniModelBuilder<Type, ValueType>::Options const& options) {
  1426. std::vector<storm::expressions::Variable> result;
  1427. if (options.isBuildAllRewardModelsSet()) {
  1428. for (auto const& variable : model.getGlobalVariables()) {
  1429. if (variable.isTransient() && (variable.isRealVariable() || variable.isUnboundedIntegerVariable())) {
  1430. result.push_back(variable.getExpressionVariable());
  1431. }
  1432. }
  1433. } else {
  1434. auto const& globalVariables = model.getGlobalVariables();
  1435. for (auto const& rewardModelName : options.getRewardModelNames()) {
  1436. if (globalVariables.hasVariable(rewardModelName)) {
  1437. result.push_back(globalVariables.getVariable(rewardModelName).getExpressionVariable());
  1438. } else {
  1439. STORM_LOG_THROW(rewardModelName.empty(), storm::exceptions::InvalidArgumentException, "Cannot build unknown reward model '" << rewardModelName << "'.");
  1440. STORM_LOG_THROW(globalVariables.getNumberOfRealTransientVariables() + globalVariables.getNumberOfUnboundedIntegerTransientVariables() == 1, storm::exceptions::InvalidArgumentException, "Reference to standard reward model is ambiguous.");
  1441. }
  1442. }
  1443. // If no reward model was yet added, but there was one that was given in the options, we try to build the
  1444. // standard reward model.
  1445. if (result.empty() && !options.getRewardModelNames().empty()) {
  1446. result.push_back(globalVariables.getTransientVariables().front().getExpressionVariable());
  1447. }
  1448. }
  1449. return result;
  1450. }
  1451. template <storm::dd::DdType Type, typename ValueType>
  1452. std::unordered_map<std::string, storm::models::symbolic::StandardRewardModel<Type, ValueType>> buildRewardModels(ComposerResult<Type, ValueType> const& system, std::vector<storm::expressions::Variable> const& rewardVariables) {
  1453. std::unordered_map<std::string, storm::models::symbolic::StandardRewardModel<Type, ValueType>> result;
  1454. for (auto const& variable : rewardVariables) {
  1455. boost::optional<storm::dd::Add<Type, ValueType>> stateRewards = boost::none;
  1456. boost::optional<storm::dd::Add<Type, ValueType>> stateActionRewards = boost::none;
  1457. boost::optional<storm::dd::Add<Type, ValueType>> transitionRewards = boost::none;
  1458. auto it = system.transientLocationAssignments.find(variable);
  1459. if (it != system.transientLocationAssignments.end()) {
  1460. stateRewards = it->second;
  1461. }
  1462. it = system.transientEdgeAssignments.find(variable);
  1463. if (it != system.transientEdgeAssignments.end()) {
  1464. stateActionRewards = it->second;
  1465. }
  1466. result.emplace(variable.getName(), storm::models::symbolic::StandardRewardModel<Type, ValueType>(stateRewards, stateActionRewards, transitionRewards));
  1467. }
  1468. return result;
  1469. }
  1470. template <storm::dd::DdType Type, typename ValueType>
  1471. std::map<std::string, storm::expressions::Expression> buildLabelExpressions(storm::jani::Model const& model, CompositionVariables<Type, ValueType> const& variables, typename DdJaniModelBuilder<Type, ValueType>::Options const& options) {
  1472. std::map<std::string, storm::expressions::Expression> result;
  1473. for (auto const& variable : model.getGlobalVariables().getTransientVariables()) {
  1474. if (variable.isBooleanVariable()) {
  1475. if (options.buildAllLabels || options.labelNames.find(variable.getName()) != options.labelNames.end()) {
  1476. result[variable.getName()] = model.getLabelExpression(variable.asBooleanVariable(), variables.automatonToLocationVariableMap);
  1477. }
  1478. }
  1479. }
  1480. return result;
  1481. }
  1482. template <storm::dd::DdType Type, typename ValueType>
  1483. std::shared_ptr<storm::models::symbolic::Model<Type, ValueType>> DdJaniModelBuilder<Type, ValueType>::build(storm::jani::Model const& model, Options const& options) {
  1484. if (model.hasUndefinedConstants()) {
  1485. std::vector<std::reference_wrapper<storm::jani::Constant const>> undefinedConstants = model.getUndefinedConstants();
  1486. std::vector<std::string> strings;
  1487. for (auto const& constant : undefinedConstants) {
  1488. std::stringstream stream;
  1489. stream << constant.get().getName() << " (" << constant.get().getType() << ")";
  1490. strings.push_back(stream.str());
  1491. }
  1492. STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Model still contains these undefined constants: " << boost::join(strings, ", ") << ".");
  1493. }
  1494. STORM_LOG_THROW(!model.usesAssignmentLevels(), storm::exceptions::InvalidSettingsException, "The symbolic JANI model builder currently does not support assignment levels.");
  1495. storm::jani::Model preparedModel = model;
  1496. // Lift the transient edge destinations. We can do so, as we know that there are no assignment levels (because that's not supported anyway).
  1497. if (preparedModel.hasTransientEdgeDestinationAssignments()) {
  1498. preparedModel.liftTransientEdgeDestinationAssignments();
  1499. }
  1500. STORM_LOG_THROW(!preparedModel.hasTransientEdgeDestinationAssignments(), storm::exceptions::WrongFormatException, "The symbolic JANI model builder currently does not support transient edge destination assignments.");
  1501. // Determine the actions that will appear in the parallel composition.
  1502. storm::jani::CompositionInformationVisitor visitor(preparedModel, preparedModel.getSystemComposition());
  1503. storm::jani::CompositionInformation actionInformation = visitor.getInformation();
  1504. // Create all necessary variables.
  1505. CompositionVariableCreator<Type, ValueType> variableCreator(preparedModel, actionInformation);
  1506. CompositionVariables<Type, ValueType> variables = variableCreator.create();
  1507. // Determine which transient assignments need to be considered in the building process.
  1508. std::vector<storm::expressions::Variable> rewardVariables = selectRewardVariables<Type, ValueType>(preparedModel, options);
  1509. // Create a builder to compose and build the model.
  1510. CombinedEdgesSystemComposer<Type, ValueType> composer(preparedModel, actionInformation, variables, rewardVariables);
  1511. ComposerResult<Type, ValueType> system = composer.compose();
  1512. // Postprocess the variables in place.
  1513. postprocessVariables(preparedModel.getModelType(), system, variables);
  1514. // Postprocess the system in place and get the states that were terminal (i.e. whose transitions were cut off).
  1515. storm::dd::Bdd<Type> terminalStates = postprocessSystem(preparedModel, system, variables, options);
  1516. // Start creating the model components.
  1517. ModelComponents<Type, ValueType> modelComponents;
  1518. // Build initial states.
  1519. modelComponents.initialStates = computeInitialStates(preparedModel, variables);
  1520. // Perform reachability analysis to obtain reachable states.
  1521. storm::dd::Bdd<Type> transitionMatrixBdd = system.transitions.notZero();
  1522. if (preparedModel.getModelType() == storm::jani::ModelType::MDP) {
  1523. transitionMatrixBdd = transitionMatrixBdd.existsAbstract(variables.allNondeterminismVariables);
  1524. }
  1525. modelComponents.reachableStates = storm::utility::dd::computeReachableStates(modelComponents.initialStates, transitionMatrixBdd, variables.rowMetaVariables, variables.columnMetaVariables);
  1526. // Check that the reachable fragment does not overlap with the illegal fragment.
  1527. storm::dd::Bdd<Type> reachableIllegalFragment = modelComponents.reachableStates && system.illegalFragment;
  1528. 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.");
  1529. // Cut transitions to reachable states.
  1530. storm::dd::Add<Type, ValueType> reachableStatesAdd = modelComponents.reachableStates.template toAdd<ValueType>();
  1531. modelComponents.transitionMatrix = system.transitions * reachableStatesAdd;
  1532. // Fix deadlocks if existing.
  1533. modelComponents.deadlockStates = fixDeadlocks(preparedModel.getModelType(), modelComponents.transitionMatrix, transitionMatrixBdd, modelComponents.reachableStates, variables);
  1534. // Cut the deadlock states by removing all states that we 'converted' to deadlock states by making them terminal.
  1535. modelComponents.deadlockStates = modelComponents.deadlockStates && !terminalStates;
  1536. // Build the reward models.
  1537. modelComponents.rewardModels = buildRewardModels(system, rewardVariables);
  1538. // Build the label to expressions mapping.
  1539. modelComponents.labelToExpressionMap = buildLabelExpressions(preparedModel, variables, options);
  1540. // Finally, create the model.
  1541. return createModel(preparedModel.getModelType(), variables, modelComponents);
  1542. }
  1543. template class DdJaniModelBuilder<storm::dd::DdType::CUDD, double>;
  1544. template class DdJaniModelBuilder<storm::dd::DdType::Sylvan, double>;
  1545. }
  1546. }