#include "src/builder/DdPrismModelBuilder.h"

#include "src/storage/dd/CuddDd.h"
#include "src/storage/dd/CuddDdManager.h"
#include "src/settings/SettingsManager.h"

#include "src/exceptions/InvalidStateException.h"

#include "src/utility/prism.h"
#include "src/utility/math.h"

namespace storm {
    namespace builder {
        
        template <storm::dd::DdType Type>
        DdPrismModelBuilder<Type>::Options::Options() : buildRewards(false), rewardModelName(), constantDefinitions() {
            // Intentionally left empty.
        }
        
        template <storm::dd::DdType Type>
        DdPrismModelBuilder<Type>::Options::Options(storm::logic::Formula const& formula) : buildRewards(formula.containsRewardOperator()), rewardModelName(), constantDefinitions(), labelsToBuild(std::set<std::string>()), expressionLabels(std::vector<storm::expressions::Expression>()) {
            // Extract all the labels used in the formula.
            std::vector<std::shared_ptr<storm::logic::AtomicLabelFormula const>> atomicLabelFormulas = formula.getAtomicLabelFormulas();
            for (auto const& formula : atomicLabelFormulas) {
                labelsToBuild.get().insert(formula.get()->getLabel());
            }
            
            // Extract all the expressions used in the formula.
            std::vector<std::shared_ptr<storm::logic::AtomicExpressionFormula const>> atomicExpressionFormulas = formula.getAtomicExpressionFormulas();
            for (auto const& formula : atomicExpressionFormulas) {
                expressionLabels.get().push_back(formula.get()->getExpression());
            }
        }
        
        template <storm::dd::DdType Type>
        void DdPrismModelBuilder<Type>::Options::addConstantDefinitionsFromString(storm::prism::Program const& program, std::string const& constantDefinitionString) {
            std::map<storm::expressions::Variable, storm::expressions::Expression> newConstantDefinitions = storm::utility::prism::parseConstantDefinitionString(program, constantDefinitionString);
            
            // If there is at least one constant that is defined, and the constant definition map does not yet exist,
            // we need to create it.
            if (!constantDefinitions && !newConstantDefinitions.empty()) {
                constantDefinitions = std::map<storm::expressions::Variable, storm::expressions::Expression>();
            }
            
            // Now insert all the entries that need to be defined.
            for (auto const& entry : newConstantDefinitions) {
                constantDefinitions.get().insert(entry);
            }
        }
        
        template <storm::dd::DdType Type>
        storm::dd::Dd<Type> DdPrismModelBuilder<Type>::createUpdateDecisionDiagram(GenerationInformation& generationInfo, storm::prism::Module const& module, storm::dd::Dd<Type> const& guard, storm::prism::Update const& update) {
            storm::dd::Dd<Type> updateDd = generationInfo.manager->getOne();
            
            // Iterate over all assignments (boolean and integer) and build the DD for it.
            std::vector<storm::prism::Assignment> assignments = update.getAssignments();
            std::set<storm::expressions::Variable> assignedVariables;
            for (auto const& assignment : assignments) {
                // Record the variable as being written.
                STORM_LOG_TRACE("Assigning to variable " << generationInfo.variableToRowMetaVariableMap.at(assignment.getVariable()).getName());
                assignedVariables.insert(generationInfo.variableToRowMetaVariableMap.at(assignment.getVariable()));
                
                // Translate the written variable.
                auto const& primedMetaVariable = generationInfo.variableToColumnMetaVariableMap.at(assignment.getVariable());
                storm::dd::Dd<Type> writtenVariable = generationInfo.manager->getIdentity(primedMetaVariable);
                
                // Translate the expression that is being assigned.
                storm::dd::Dd<Type> updateExpression = generationInfo.rowExpressionAdapter->translateExpression(assignment.getExpression());
                
                // Combine the update expression with the guard.
                storm::dd::Dd<Type> result = updateExpression * guard;
                
                // Combine the variable and the assigned expression.
                result = result.equals(writtenVariable);
                result *= guard;
                
                // Restrict the transitions to the range of the written variable.
                result = result * generationInfo.manager->getRange(primedMetaVariable);
                
                updateDd *= result;
            }
            
            // FIXME: global boolean variables cause problems. They cannot be added here, because then synchronization goes wrong.
//            for (uint_fast64_t i = 0; i < generationInfo.program.getGlobalBooleanVariables().size(); ++i) {
//                storm::prism::BooleanVariable const& booleanVariable = generationInfo.program.getGlobalBooleanVariables().at(i);
//                if (update.getAssignmentMapping().find(booleanVariable.getName()) == update.getAssignmentMapping().end()) {
//                    // Multiply identity matrix
//                    updateDd = updateDd * generationInfo.variableToIdentityDecisionDiagramMap.at(booleanVariable.getName());
//                }
//            }
//            
//            // All unused global integer variables do not change
//            for (uint_fast64_t i = 0; i < generationInfo.program.getGlobalIntegerVariables().size(); ++i) {
//                storm::prism::IntegerVariable const& integerVariable = generationInfo.program.getGlobalIntegerVariables().at(i);
//                if (update.getAssignmentMapping().find(integerVariable.getName()) == update.getAssignmentMapping().end()) {
//                    // Multiply identity matrix
//                    updateDd = updateDd * generationInfo.variableToIdentityDecisionDiagramMap.at(integerVariable.getName());
//                }
//            }
            
            // All unassigned boolean variables need to keep their value.
            for (storm::prism::BooleanVariable const& booleanVariable : module.getBooleanVariables()) {
                storm::expressions::Variable const& metaVariable = generationInfo.variableToRowMetaVariableMap.at(booleanVariable.getExpressionVariable());
                if (assignedVariables.find(metaVariable) == assignedVariables.end()) {
                    STORM_LOG_TRACE("Multiplying identity of variable " << booleanVariable.getName());
                    updateDd *= generationInfo.variableToIdentityMap.at(booleanVariable.getExpressionVariable());
                }
            }
            
            // All unassigned integer variables need to keep their value.
            for (storm::prism::IntegerVariable const& integerVariable : module.getIntegerVariables()) {
                storm::expressions::Variable const& metaVariable = generationInfo.variableToRowMetaVariableMap.at(integerVariable.getExpressionVariable());
                if (assignedVariables.find(metaVariable) == assignedVariables.end()) {
                    STORM_LOG_TRACE("Multiplying identity of variable " << integerVariable.getName());
                    updateDd *= generationInfo.variableToIdentityMap.at(integerVariable.getExpressionVariable());
                }
            }
            
            return updateDd;
        }
        
        template <storm::dd::DdType Type>
        typename DdPrismModelBuilder<Type>::ActionDecisionDiagram DdPrismModelBuilder<Type>::createCommandDecisionDiagram(GenerationInformation& generationInfo, storm::prism::Module const& module, storm::prism::Command const& command) {
            STORM_LOG_TRACE("Translating guard " << command.getGuardExpression());
            storm::dd::Dd<Type> guardDd = generationInfo.rowExpressionAdapter->translateExpression(command.getGuardExpression());
            STORM_LOG_WARN_COND(!guardDd.isZero(), "The guard '" << command.getGuardExpression() << "' is unsatisfiable.");
            
            if (!guardDd.isZero()) {
                storm::dd::Dd<Type> commandDd = generationInfo.manager->getZero();
                for (storm::prism::Update const& update : command.getUpdates()) {
                    storm::dd::Dd<Type> updateDd = createUpdateDecisionDiagram(generationInfo, module, guardDd, update);
                    
                    STORM_LOG_WARN_COND(!updateDd.isZero(), "Update '" << update << "' does not have any effect.");
                    
                    storm::dd::Dd<Type> probabilityDd = generationInfo.rowExpressionAdapter->translateExpression(update.getLikelihoodExpression());
                    updateDd *= probabilityDd;
                    
                    commandDd += updateDd;
                }
                
                guardDd.exportToDot(module.getName() + "_cmd_" + std::to_string(command.getGlobalIndex()) + "_grd.dot");
                commandDd.exportToDot(module.getName() + "_cmd_" + std::to_string(command.getGlobalIndex()) + ".dot");
                
                return ActionDecisionDiagram(guardDd, guardDd * commandDd);
            } else {
                return ActionDecisionDiagram(*generationInfo.manager);
            }
        }
        
        template <storm::dd::DdType Type>
        typename DdPrismModelBuilder<Type>::ActionDecisionDiagram DdPrismModelBuilder<Type>::createActionDecisionDiagram(GenerationInformation& generationInfo, storm::prism::Module const& module, boost::optional<uint_fast64_t> synchronizationActionIndex, uint_fast64_t nondeterminismVariableOffset) {
            std::vector<ActionDecisionDiagram> commandDds;
            for (storm::prism::Command const& command : module.getCommands()) {
                
                // Determine whether the command is relevant for the selected action.
                bool relevant = (!synchronizationActionIndex && !command.isLabeled()) || (synchronizationActionIndex && command.getActionIndex() == synchronizationActionIndex.get());
                
                if (!relevant) {
                    continue;
                }
                
                // At this point, the command is known to be relevant for the action.
                commandDds.push_back(createCommandDecisionDiagram(generationInfo, module, command));
            }
            
            ActionDecisionDiagram result(*generationInfo.manager);
            if (!commandDds.empty()) {
                switch (generationInfo.program.getModelType()){
                    case storm::prism::Program::ModelType::DTMC:
                        result = combineCommandsToActionDTMC(generationInfo, commandDds);
                        break;
                    case storm::prism::Program::ModelType::MDP:
                        result = combineCommandsToActionMDP(generationInfo, commandDds, nondeterminismVariableOffset);
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Cannot translate model of this type.");
                }
            }
            
            return result;
        }
        
        template <storm::dd::DdType Type>
        typename DdPrismModelBuilder<Type>::ActionDecisionDiagram DdPrismModelBuilder<Type>::combineCommandsToActionDTMC(GenerationInformation& generationInfo, std::vector<ActionDecisionDiagram> const& commandDds) {
            storm::dd::Dd<Type> allGuards = generationInfo.manager->getZero();
            storm::dd::Dd<Type> allCommands = generationInfo.manager->getZero();
            storm::dd::Dd<Type> temporary;
            
            for (auto const& commandDd : commandDds) {
                // Check for overlapping guards.
                temporary = commandDd.guardDd * allGuards;
                STORM_LOG_WARN_COND(temporary.isZero(), "Guard of a command overlaps with previous guards.");
                
                allGuards += commandDd.guardDd;
                allCommands += commandDd.guardDd * commandDd.transitionsDd;
            }
            
            return ActionDecisionDiagram(allGuards, allCommands);
        }
        
        template <storm::dd::DdType Type>
        storm::dd::Dd<Type> DdPrismModelBuilder<Type>::encodeChoice(GenerationInformation& generationInfo, uint_fast64_t nondeterminismVariableOffset, uint_fast64_t numberOfBinaryVariables, int_fast64_t value) {
            storm::dd::Dd<Type> result = generationInfo.manager->getOne();
            
            std::map<storm::expressions::Variable, int_fast64_t> metaVariableNameToValueMap;
            for (uint_fast64_t i = nondeterminismVariableOffset; i < nondeterminismVariableOffset + numberOfBinaryVariables; ++i) {
                if (value & (1ull << (numberOfBinaryVariables - i - 1))) {
                    metaVariableNameToValueMap.emplace(generationInfo.nondeterminismMetaVariables[i], 1);
                }
                else {
                    metaVariableNameToValueMap.emplace(generationInfo.nondeterminismMetaVariables[i], 0);
                }
            }
            
            result.setValue(metaVariableNameToValueMap, 1);
            return result;
        }
        
        template <storm::dd::DdType Type>
        typename DdPrismModelBuilder<Type>::ActionDecisionDiagram DdPrismModelBuilder<Type>::combineCommandsToActionMDP(GenerationInformation& generationInfo, std::vector<ActionDecisionDiagram> const& commandDds, uint_fast64_t nondeterminismVariableOffset) {
            storm::dd::Dd<Type> allGuards = generationInfo.manager->getZero();
            storm::dd::Dd<Type> allCommands = generationInfo.manager->getZero();
            
            // Sum all guards, so we can read off the maximal number of nondeterministic choices in any given state.
            storm::dd::Dd<Type> sumOfGuards = generationInfo.manager->getZero();
            for (auto const& commandDd : commandDds) {
                sumOfGuards += commandDd.guardDd;
                allGuards = allGuards || commandDd.guardDd;
            }
            uint_fast64_t maxChoices = static_cast<uint_fast64_t>(sumOfGuards.getMax());

            // Depending on the maximal number of nondeterminstic choices, we need to use some variables to encode the nondeterminism.
            if (maxChoices == 0) {
                return ActionDecisionDiagram(*generationInfo.manager);
            } else if (maxChoices == 1) {
                // Sum up all commands.
                for (auto const& commandDd : commandDds) {
                    // FIXME: necessary to multiply with guard again?
                    allCommands += commandDd.guardDd * commandDd.transitionsDd;
                }
                return ActionDecisionDiagram(sumOfGuards, allCommands);
            } else {
                // Calculate number of required variables to encode the nondeterminism.
                uint_fast64_t numberOfBinaryVariables = static_cast<uint_fast64_t>(std::ceil(storm::utility::math::log2(maxChoices)));
                
                storm::dd::Dd<Type> equalsNumberOfChoicesDd = generationInfo.manager->getZero();
                std::vector<storm::dd::Dd<Type>> choiceDds(maxChoices, generationInfo.manager->getZero());
                std::vector<storm::dd::Dd<Type>> remainingDds(maxChoices, generationInfo.manager->getZero());
                
                for (uint_fast64_t currentChoices = 1; currentChoices <= maxChoices; ++currentChoices) {
                    // Determine the set of states with exactly currentChoices choices.
                    equalsNumberOfChoicesDd = sumOfGuards.equals(generationInfo.manager->getConstant(static_cast<double>(currentChoices)));
                    
                    // If there is no such state, continue with the next possible number of choices.
                    if (equalsNumberOfChoicesDd.isZero()) {
                        continue;
                    }
                    
                    // Reset the previously used intermediate storage.
                    for (uint_fast64_t j = 0; j < currentChoices; ++j) {
                        choiceDds[j] = generationInfo.manager->getZero();
                        remainingDds[j] = equalsNumberOfChoicesDd;
                    }
                    
                    for (std::size_t j = 0; j < commandDds.size(); ++j) {
                        // Check if command guard overlaps with equalsNumberOfChoicesDd. That is, there are states with exactly currentChoices
                        // choices such that one outgoing choice is given by the j-th command.
                        storm::dd::Dd<Type> guardChoicesIntersection = commandDds[j].guardDd && equalsNumberOfChoicesDd;
                        
                        // If there is no such state, continue with the next command.
                        if (guardChoicesIntersection.isZero()) {
                            continue;
                        }
                        
                        // Split the currentChoices nondeterministic choices.
                        for (uint_fast64_t k = 0; k < currentChoices; ++k) {
                            // Calculate the overlapping part of command guard and the remaining DD.
                            storm::dd::Dd<Type> remainingGuardChoicesIntersection = guardChoicesIntersection && remainingDds[k];
                            
                            // Check if we can add some overlapping parts to the current index
                            if (!remainingGuardChoicesIntersection.isZero()) {
                                // Remove overlapping parts from the remaining DD.
                                remainingDds[k] = remainingDds[k] && !remainingGuardChoicesIntersection;
                                
                                // Combine the overlapping part of the guard with command updates and add it to the resulting DD.
                                choiceDds[k] += remainingGuardChoicesIntersection * commandDds[j].transitionsDd;
                            }
                            
                            // Remove overlapping parts from the command guard DD
                            guardChoicesIntersection = guardChoicesIntersection && !remainingGuardChoicesIntersection;
                            
                            // If the guard DD has become equivalent to false, we can stop here.
                            if (guardChoicesIntersection.isZero()) {
                                break;
                            }
                        }
                    }
                    
                    // Add the meta variables that encode the nondeterminisim to the different choices.
                    for (uint_fast64_t j = 0; j < currentChoices; ++j) {
                        allCommands += encodeChoice(generationInfo, nondeterminismVariableOffset, numberOfBinaryVariables, j) * choiceDds[j];
                    }
                    
                    // Delete currentChoices out of overlapping DD
                    sumOfGuards = sumOfGuards * (!equalsNumberOfChoicesDd);
                }
                
                return ActionDecisionDiagram(allGuards, allCommands, nondeterminismVariableOffset + numberOfBinaryVariables);
            }
        }

        template <storm::dd::DdType Type>
        typename DdPrismModelBuilder<Type>::ActionDecisionDiagram DdPrismModelBuilder<Type>::combineSynchronizingActions(GenerationInformation const& generationInfo, ActionDecisionDiagram const& action1, ActionDecisionDiagram const& action2) {
            return ActionDecisionDiagram(action1.guardDd * action2.guardDd, action1.transitionsDd * action2.transitionsDd, std::max(action1.numberOfUsedNondeterminismVariables, action2.numberOfUsedNondeterminismVariables));
        }
        
        template <storm::dd::DdType Type>
        typename DdPrismModelBuilder<Type>::ActionDecisionDiagram DdPrismModelBuilder<Type>::combineUnsynchronizedActions(GenerationInformation const& generationInfo, ActionDecisionDiagram const& action1, ActionDecisionDiagram const& action2, storm::dd::Dd<Type> const& identityDd1, storm::dd::Dd<Type> const& identityDd2) {
            storm::dd::Dd<Type> action1Extended = action1.transitionsDd * identityDd2;
            storm::dd::Dd<Type> action2Extended = action2.transitionsDd * identityDd1;

            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::DTMC) {
                return ActionDecisionDiagram(action1.guardDd + action2.guardDd, action1Extended + action2Extended, 0);
            } else if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
                if (action1.transitionsDd.isZero()) {
                    return ActionDecisionDiagram(action2.guardDd, action2Extended, action2.numberOfUsedNondeterminismVariables);
                } else if (action2.transitionsDd.isZero()) {
                    return ActionDecisionDiagram(action1.guardDd, action1Extended, action1.numberOfUsedNondeterminismVariables);
                }
                
                // Bring both choices to the same number of variables that encode the nondeterminism.
                uint_fast64_t numberOfUsedNondeterminismVariables = std::max(action1.numberOfUsedNondeterminismVariables, action2.numberOfUsedNondeterminismVariables);
                if (action1.numberOfUsedNondeterminismVariables > action2.numberOfUsedNondeterminismVariables) {
                    storm::dd::Dd<Type> nondeterminisimEncoding = generationInfo.manager->getOne();
                    
                    for (uint_fast64_t i = action2.numberOfUsedNondeterminismVariables + 1; i <= action1.numberOfUsedNondeterminismVariables; ++i) {
                        nondeterminisimEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0);
                    }
                    action2Extended *= nondeterminisimEncoding;
                } else if (action2.numberOfUsedNondeterminismVariables > action1.numberOfUsedNondeterminismVariables) {
                    storm::dd::Dd<Type> nondeterminisimEncoding = generationInfo.manager->getOne();
                    
                    for (uint_fast64_t i = action1.numberOfUsedNondeterminismVariables + 1; i <= action2.numberOfUsedNondeterminismVariables; ++i) {
                        nondeterminisimEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0);
                    }
                    action1Extended *= nondeterminisimEncoding;
                }
                
                // Add a new variable that resolves the nondeterminism between the two choices.
                storm::dd::Dd<Type> combinedTransitions = generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[numberOfUsedNondeterminismVariables + 1], 1).ite(action2Extended, action1Extended);
                
                return ActionDecisionDiagram(action1.guardDd || action2.guardDd, combinedTransitions, numberOfUsedNondeterminismVariables + 1);
            } else {
                STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Illegal model type.");
            }
        }

        template <storm::dd::DdType Type>
        typename DdPrismModelBuilder<Type>::ModuleDecisionDiagram DdPrismModelBuilder<Type>::createModuleDecisionDiagram(GenerationInformation& generationInfo, storm::prism::Module const& module, std::map<uint_fast64_t, uint_fast64_t> const& synchronizingActionToOffsetMap) {
            // Start by creating the action DD for the independent action.
            ActionDecisionDiagram independentActionDd = createActionDecisionDiagram(generationInfo, module, boost::optional<uint_fast64_t>(), 0);
            
            // Create module DD for all synchronizing actions of the module.
            std::map<uint_fast64_t, ActionDecisionDiagram> actionIndexToDdMap;
            for (auto const& actionIndex : module.getActionIndices()) {
                STORM_LOG_TRACE("Creating DD for action '" << actionIndex << "'.");
                actionIndexToDdMap.emplace(actionIndex, createActionDecisionDiagram(generationInfo, module, actionIndex, synchronizingActionToOffsetMap.at(actionIndex)));
            }
            
            return ModuleDecisionDiagram(independentActionDd, actionIndexToDdMap, generationInfo.moduleToIdentityMap.at(module.getName()));
        }
        
        template <storm::dd::DdType Type>
        storm::dd::Dd<Type> DdPrismModelBuilder<Type>::createSystemFromModule(GenerationInformation& generationInfo, ModuleDecisionDiagram const& module) {
            // If the model is an MDP, we need to encode the nondeterminism using additional variables.
            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
                storm::dd::Dd<Type> result = generationInfo.manager->getZero();
                
                // First, determine the maximal variable index that is used.
                uint_fast64_t numberOfUsedNondeterminismVariables = module.independentAction.numberOfUsedNondeterminismVariables;
                for (auto const& synchronizingAction : module.synchronizingActionToDecisionDiagramMap) {
                    numberOfUsedNondeterminismVariables = std::max(numberOfUsedNondeterminismVariables, synchronizingAction.second.numberOfUsedNondeterminismVariables);
                }

                // Now make all actions use the same amount of nondeterminism variables.
                
                // Add variables to independent action DD.
                storm::dd::Dd<Type> nondeterminismEncoding = generationInfo.manager->getOne();
                for (uint_fast64_t i = module.independentAction.numberOfUsedNondeterminismVariables + 1; i <= numberOfUsedNondeterminismVariables; ++i) {
                    nondeterminismEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0);
                }
                result = module.independentAction.transitionsDd * nondeterminismEncoding;

                // Add variables to synchronized action DDs.
                std::map<uint_fast64_t, storm::dd::Dd<Type>> synchronizingActionToDdMap;
                for (auto const& synchronizingAction : module.synchronizingActionToDecisionDiagramMap) {
                    nondeterminismEncoding = generationInfo.manager->getOne();
                    for (uint_fast64_t i = synchronizingAction.second.numberOfUsedNondeterminismVariables + 1; i <= numberOfUsedNondeterminismVariables; ++i) {
                        nondeterminismEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0);
                    }
                    synchronizingActionToDdMap.emplace(synchronizingAction.first, synchronizingAction.second.transitionsDd * nondeterminismEncoding);
                }
                
                // Add variables for synchronization.
                storm::dd::Dd<Type> synchronization = generationInfo.manager->getOne();
                for (uint_fast64_t i = 0; i < generationInfo.synchronizationMetaVariables.size(); ++i) {
                    synchronization *= generationInfo.manager->getEncoding(generationInfo.synchronizationMetaVariables[i], 0);
                }
                result *= synchronization;
                
                for (auto& synchronizingAction : synchronizingActionToDdMap) {
                    synchronization = generationInfo.manager->getOne();
                    
                    for (uint_fast64_t i = 0; i < generationInfo.synchronizationMetaVariables.size(); ++i) {
                        if (i == synchronizingAction.first) {
                            synchronization *= generationInfo.manager->getEncoding(generationInfo.synchronizationMetaVariables[i], 1);
                        } else {
                            synchronization *= generationInfo.manager->getEncoding(generationInfo.synchronizationMetaVariables[i], 0);
                        }
                    }
                    
                    synchronizingAction.second *= synchronization;
                }
                
                // Now, we can simply add all synchronizing actions to the result.
                for (auto const& synchronizingAction : module.synchronizingActionToDecisionDiagramMap) {
                    result += synchronizingAction.second.transitionsDd;
                }

                return result;
            } else {
                // Simply add all actions.
                storm::dd::Dd<Type> result = module.independentAction.transitionsDd;
                for (auto const& synchronizingAction : module.synchronizingActionToDecisionDiagramMap) {
                    result += synchronizingAction.second.transitionsDd;
                }
                return result;
            }
        }
        
        template <storm::dd::DdType Type>
        storm::dd::Dd<Type> DdPrismModelBuilder<Type>::createSystemDecisionDiagram(GenerationInformation& generationInfo) {
            // Create the initial offset mapping.
            std::map<uint_fast64_t, uint_fast64_t> synchronizingActionToOffsetMap;
            for (auto const& actionIndex : generationInfo.program.getActionIndices()) {
                synchronizingActionToOffsetMap[actionIndex] = 0;
            }

            // Start by creating DDs for the first module.
            STORM_LOG_TRACE("Translating (first) module '" << generationInfo.program.getModule(0).getName() << "'.");
            ModuleDecisionDiagram system = createModuleDecisionDiagram(generationInfo, generationInfo.program.getModule(0), synchronizingActionToOffsetMap);

            // No translate module by module and combine it with the system created thus far.
            for (uint_fast64_t i = 1; i < generationInfo.program.getNumberOfModules(); ++i) {
                storm::prism::Module const& currentModule = generationInfo.program.getModule(i);
                STORM_LOG_TRACE("Translating module '" << currentModule.getName() << "'.");
                
                // Update the offset index.
                for (auto const& actionIndex : generationInfo.program.getActionIndices()) {
                    if (system.hasSynchronizingAction(actionIndex)) {
                        synchronizingActionToOffsetMap[actionIndex] = system.synchronizingActionToDecisionDiagramMap[actionIndex].numberOfUsedNondeterminismVariables;
                    }
                }
                
                ModuleDecisionDiagram nextModule = createModuleDecisionDiagram(generationInfo, currentModule, synchronizingActionToOffsetMap);
                
                // Combine the non-synchronizing action.
                system.independentAction = combineUnsynchronizedActions(generationInfo, system.independentAction, nextModule.independentAction, system.identity, nextModule.identity);
                
                // Combine synchronizing actions.
                for (auto const& actionIndex : currentModule.getActionIndices()) {
                    std::cout << "treating action index " << actionIndex << std::endl;
                    if (system.hasSynchronizingAction(actionIndex)) {
                        system.synchronizingActionToDecisionDiagramMap[actionIndex] = combineSynchronizingActions(generationInfo, system.synchronizingActionToDecisionDiagramMap[actionIndex], nextModule.synchronizingActionToDecisionDiagramMap[actionIndex]);
                    } else {
                        system.synchronizingActionToDecisionDiagramMap[actionIndex] = combineUnsynchronizedActions(generationInfo, ActionDecisionDiagram(*generationInfo.manager), nextModule.synchronizingActionToDecisionDiagramMap[actionIndex], system.identity, nextModule.identity);
                    }
                }
                
                // Combine identity matrices.
                system.identity = system.identity * nextModule.identity;
            }
            
            storm::dd::Dd<Type> result = createSystemFromModule(generationInfo, system);
            
            // For DTMCs, we normalize each row to 1 (to account for non-determinism).
            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::DTMC) {
                result = result / result.sumAbstract(generationInfo.columnMetaVariables);
            }
            
            return result;
        }
        
        template <storm::dd::DdType Type>
        void DdPrismModelBuilder<Type>::translateProgram(storm::prism::Program const& program, Options const& options) {
            // There might be nondeterministic variables. In that case the program must be prepared before translating.
            storm::prism::Program preparedProgram;
            if (options.constantDefinitions) {
                preparedProgram = program.defineUndefinedConstants(options.constantDefinitions.get());
            } else {
                preparedProgram = program;
            }
            
            preparedProgram = preparedProgram.substituteConstants();
            
            std::cout << "translated prog: " << preparedProgram << std::endl;
            
            // Start by initializing the structure used for storing all information needed during the model generation.
            // In particular, this creates the meta variables used to encode the model.
            GenerationInformation generationInfo(preparedProgram);

            auto clock = std::chrono::high_resolution_clock::now();
            storm::dd::Dd<Type> transitionMatrix = createSystemDecisionDiagram(generationInfo);
            std::cout << "Built transition matrix in " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - clock).count() << "ms." << std::endl;
            transitionMatrix.exportToDot("trans.dot");
            std::cout << "num transitions: " << transitionMatrix.getNonZeroCount() << std::endl;
            
            storm::dd::Dd<Type> initialStates = createInitialStatesDecisionDiagram(generationInfo);
            std::cout << "initial states: " << initialStates.getNonZeroCount() << std::endl;
            
            storm::dd::Dd<Type> reachableStates = computeReachableStates(generationInfo, initialStates, transitionMatrix);
            std::cout << "reachable states: " << reachableStates.getNonZeroCount() << std::endl;
            exit(-1);
        }
        
        template <storm::dd::DdType Type>
        storm::dd::Dd<Type> DdPrismModelBuilder<Type>::createInitialStatesDecisionDiagram(GenerationInformation& generationInfo) {
            storm::dd::Dd<Type> initialStates = generationInfo.rowExpressionAdapter->translateExpression(generationInfo.program.getInitialConstruct().getInitialStatesExpression());
            
            for (auto const& metaVariable : generationInfo.rowMetaVariables) {
                initialStates *= generationInfo.manager->getRange(metaVariable);
            }
            
            return initialStates;
        }
        
        template <storm::dd::DdType Type>
        storm::dd::Dd<Type> DdPrismModelBuilder<Type>::computeReachableStates(GenerationInformation& generationInfo, storm::dd::Dd<Type> const& initialStates, storm::dd::Dd<Type> const& transitions) {
            storm::dd::Dd<Type> reachableStatesBdd = initialStates.notZero();
            
            // If the model is an MDP, we can abstract from the variables encoding the nondeterminism in the model.
            storm::dd::Dd<Type> transitionBdd = transitions.notZero();
            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
                std::set<storm::expressions::Variable> abstractVariables;
                abstractVariables.insert(generationInfo.synchronizationMetaVariables.begin(), generationInfo.synchronizationMetaVariables.end());
                abstractVariables.insert(generationInfo.nondeterminismMetaVariables.begin(), generationInfo.nondeterminismMetaVariables.end());
                transitionBdd = transitionBdd.existsAbstract(abstractVariables);
            }
            
            transitionBdd.exportToDot("trans.dot");
            reachableStatesBdd.exportToDot("reach.dot");
            
            // Perform the BFS to discover all reachable states.
            bool changed = true;
            uint_fast64_t iteration = 0;
            do {
                STORM_LOG_TRACE("Iteration " << iteration << " of computing reachable states.");
                changed = false;
                storm::dd::Dd<Type> tmp = reachableStatesBdd * transitionBdd;
                tmp = tmp.existsAbstract(generationInfo.rowMetaVariables);
                tmp.swapVariables(generationInfo.rowColumnMetaVariablePairs);

                storm::dd::Dd<Type> newReachableStates = tmp * (!reachableStatesBdd);
                
                // Check whether new states were indeed discovered.
                if (!newReachableStates.isZero()) {
                    changed = true;
                }
                
                reachableStatesBdd += newReachableStates;
                ++iteration;
            } while (changed);
            
            return reachableStatesBdd;
        }
        
//        template <storm::dd::DdType Type>
//        storm::dd::Dd<Type> SymbolicModelAdapter<Type>::createSystemDecisionDiagramm(GenerationInformation & generationInfo){
//            
//            uint_fast64_t numberOfSynchronizingActions = generationInfo.allSynchronizingActions.size();
//            
//            // System DDs
//            SystemComponentDecisionDiagram<Type> systemDds(0);
//            SystemComponentDecisionDiagram<Type> systemDds1(0);
//            SystemComponentDecisionDiagram<Type> systemDds2(0);
//            
//            storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//            
//            // Initialize usedNondetVariablesVector
//            // TODO: Formulate simpler.
//            std::vector<uint_fast64_t> usedNondetVariablesVector(numberOfSynchronizingActions);
//            for (uint_fast64_t j = 0; j < numberOfSynchronizingActions; ++j) {
//                usedNondetVariablesVector[j] = 0;
//            }
//            
//            // Create DD for first module
//            systemDds = createSystemComponentDecisionDiagramm(generationInfo, generationInfo.program.getModule(0), usedNondetVariablesVector);
//            
//            for (uint_fast64_t i = 1; i < generationInfo.program.getNumberOfModules(); ++i) {
//                
//                // Create new usedNondetVariablesVector
//                std::vector<uint_fast64_t> newUsedNondetVariablesVector(numberOfSynchronizingActions);
//                for (uint_fast64_t j = 0; j < numberOfSynchronizingActions; ++j) {
//                    // Check if systemDds contains action
//                    if (std::find(systemDds.allSynchronizingActions.begin(), systemDds.allSynchronizingActions.end(), generationInfo.allSynchronizingActions[j]) != systemDds.allSynchronizingActions.end()) {
//                        newUsedNondetVariablesVector[j] = systemDds.synchronizingActionDds[j].usedNondetVariables;
//                    }
//                    else{
//                        newUsedNondetVariablesVector[j] = usedNondetVariablesVector[j];
//                    }
//                }
//                
//                // Create DD for next module
//                systemDds2 = createSystemComponentDecisionDiagramm(generationInfo, generationInfo.program.getModule(i), newUsedNondetVariablesVector);
//                
//                // SystemDds1 stores the previous modules (already combined)
//                systemDds1 = SystemComponentDecisionDiagram<Type>(systemDds);
//                
//                // SystemDds is used to store combination of SystemDds1 and SystemDds2
//                systemDds = SystemComponentDecisionDiagram<Type>(numberOfSynchronizingActions);
//                
//                // Combine non-synchronizing/independent actions
//                systemDds.independentActionDd = combineModules(generationInfo, false, systemDds1.independentActionDd, systemDds2.independentActionDd, systemDds1.identityMatrix, systemDds2.identityMatrix);
//                
//                // Combine synchronizing actions
//                for (uint_fast64_t j = 0; j < numberOfSynchronizingActions; ++j){
//                    // Check if both modules contain the current action
//                    if (std::find(systemDds1.allSynchronizingActions.begin(), systemDds1.allSynchronizingActions.end(), generationInfo.allSynchronizingActions[j]) != systemDds1.allSynchronizingActions.end() &&
//                        std::find(systemDds2.allSynchronizingActions.begin(), systemDds2.allSynchronizingActions.end(), generationInfo.allSynchronizingActions[j]) != systemDds2.allSynchronizingActions.end()) {
//                        // Both modules contain action
//                        systemDds.synchronizingActionDds[j] = combineModules(generationInfo, true, systemDds1.synchronizingActionDds[j], systemDds2.synchronizingActionDds[j], systemDds1.identityMatrix, systemDds2.identityMatrix);
//                    }
//                    else {
//                        // Only one or no module contains current action
//                        systemDds.synchronizingActionDds[j] = combineModules(generationInfo, false, systemDds1.synchronizingActionDds[j], systemDds2.synchronizingActionDds[j], systemDds1.identityMatrix, systemDds2.identityMatrix);
//                    }
//                }
//                
//                // Combine identity matrix
//                systemDds.identityMatrix = systemDds1.identityMatrix * systemDds2.identityMatrix;
//                
//                // Combine list of synchronizing actions
//                systemDds.allSynchronizingActions.insert(systemDds1.allSynchronizingActions.begin(), systemDds1.allSynchronizingActions.end());
//                systemDds.allSynchronizingActions.insert(systemDds2.allSynchronizingActions.begin(), systemDds2.allSynchronizingActions.end());
//            }
//            
//            // Combine all DDs
//            systemDds = combineSystem(generationInfo, systemDds);
//            generationInfo.globalSystemDds = SystemComponentDecisionDiagram<Type>(systemDds);
//            
//            
//            // Build state and transition rewards
//            generationInfo.rewardDds = computeRewards(generationInfo, systemDds);
//            
//            // Create transition to action mapping for MDPs
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                generationInfo.transitionActionDd = computeTransitionAction(generationInfo, systemDds);
//            }
//            
//            // Normalize each row for DTMCs
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::DTMC) {
//                temporary = storm::dd::Dd<Type>(systemDds.independentActionDd.commandsDd);
//                temporary = temporary.sumAbstract(generationInfo.columnMetaVariableNames);
//                systemDds.independentActionDd.commandsDd = systemDds.independentActionDd.commandsDd / temporary;
//            }
//            
//            // Get system transition matrix
//            storm::dd::Dd<Type> systemTransitionMatrix = systemDds.independentActionDd.commandsDd;
//            
//            return systemTransitionMatrix;
//        }
//
//        template <storm::dd::DdType Type>
//        SystemComponentDecisionDiagram<Type> SymbolicModelAdapter<Type>::combineSystem(GenerationInformation const & generationInfo, SystemComponentDecisionDiagram<Type> systemDds){
//            
//            uint_fast64_t numberOfSynchronizingActions = generationInfo.allSynchronizingActions.size();
//            uint_fast64_t max = 0;
//            storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//            storm::dd::Dd<Type> variableDd = generationInfo.manager->getZero();
//            
//            // Add non-determinism variables for MDPs
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                // Check DD variables
//                
//                // Look for maximal variable index
//                max = systemDds.independentActionDd.usedNondetVariables;
//                for (uint_fast64_t i = 0; i < numberOfSynchronizingActions; ++i){
//                    if (systemDds.synchronizingActionDds[i].usedNondetVariables > max) {
//                        max = systemDds.synchronizingActionDds[i].usedNondetVariables;
//                    }
//                }
//                
//                // Add variables to independent action DD (if required)
//                if (max > systemDds.independentActionDd.usedNondetVariables) {
//                    temporary = generationInfo.manager->getOne();
//                    for (uint_fast64_t i = systemDds.independentActionDd.usedNondetVariables+1; i <= max; ++i){
//                        
//                        // Get variable and set to 0
//                        variableDd = generationInfo.manager->getEncoding("nondet" + std::to_string(i), 0);
//                        temporary = temporary * variableDd;
//                        
//                    }
//                    systemDds.independentActionDd.commandsDd = systemDds.independentActionDd.commandsDd * temporary;
//                    systemDds.independentActionDd.usedNondetVariables = max;
//                }
//                
//                // Add variables to synchronized action DDs
//                for (uint_fast64_t j = 0; j < numberOfSynchronizingActions; ++j){
//                    if (max > systemDds.synchronizingActionDds[j].usedNondetVariables) {
//                        temporary = generationInfo.manager->getOne();
//                        for (uint_fast64_t i = systemDds.synchronizingActionDds[j].usedNondetVariables+1; i <= max; ++i){
//                            
//                            // Get variable and set to 0
//                            variableDd = generationInfo.manager->getEncoding("nondet" + std::to_string(i), 0);
//                            temporary = temporary * variableDd;
//                            
//                        }
//                        systemDds.synchronizingActionDds[j].commandsDd = systemDds.synchronizingActionDds[j].commandsDd * temporary;
//                        systemDds.synchronizingActionDds[j].usedNondetVariables = max;
//                    }
//                }
//                
//                // Set variables for synchronization
//                temporary = generationInfo.manager->getOne();
//                for (uint_fast64_t i = 0; i < numberOfSynchronizingActions; ++i){
//                    // Get sync variable
//                    variableDd = generationInfo.manager->getEncoding("sync" + std::to_string(i), 0);
//                    temporary = temporary * variableDd;
//                }
//                
//                systemDds.independentActionDd.commandsDd = temporary * systemDds.independentActionDd.commandsDd;
//                
//                // Set variables for synchronized action DDs
//                for (uint_fast64_t i = 0; i < numberOfSynchronizingActions; ++i){
//                    temporary = generationInfo.manager->getOne();
//                    for (uint_fast64_t j = 0; j < numberOfSynchronizingActions; ++j){
//                        // Enable synchronizing variable j iff current action i==j
//                        if (i == j) {
//                            variableDd = generationInfo.manager->getEncoding("sync" + std::to_string(j), 1);
//                            temporary = temporary * variableDd;
//                        }
//                        else {
//                            variableDd = generationInfo.manager->getEncoding("sync" + std::to_string(j), 0);
//                            temporary = temporary * variableDd;
//                        }
//                    }
//                    
//                    systemDds.synchronizingActionDds[i].commandsDd = temporary * systemDds.synchronizingActionDds[i].commandsDd;
//                }
//                
//            }
//            
//            // Create transition matrix
//            temporary = systemDds.independentActionDd.commandsDd;
//            for (uint_fast64_t i = 0; i < numberOfSynchronizingActions; ++i){
//                temporary = temporary + systemDds.synchronizingActionDds[i].commandsDd;
//            }
//            
//            // Store transition matrix in systemDDs structure
//            systemDds.independentActionDd.commandsDd = temporary;
//            
//            return systemDds;
//        }
//        
//        template <storm::dd::DdType Type>
//        ModuleDecisionDiagram<Type> SymbolicModelAdapter<Type>::combineModules(GenerationInformation const & generationInfo, bool synchronizing, ModuleDecisionDiagram<Type> moduleDd1, ModuleDecisionDiagram<Type> moduleDd2, storm::dd::Dd<Type> const& identityDd1, storm::dd::Dd<Type> const& identityDd2){
//            
//            // Module DD
//            ModuleDecisionDiagram<Type> moduleDd = ModuleDecisionDiagram<Type>();
//            
//            if (synchronizing) {
//                // Synchronizing actions
//                
//                // Combine guards (intersection)
//                moduleDd.guardDd = moduleDd1.guardDd * moduleDd2.guardDd;
//                
//                // Combine transitions
//                moduleDd.commandsDd = moduleDd1.commandsDd * moduleDd2.commandsDd;
//                
//                // Update min/max index
//                moduleDd.usedNondetVariables = (moduleDd1.usedNondetVariables > moduleDd2.usedNondetVariables) ? moduleDd1.usedNondetVariables : moduleDd2.usedNondetVariables;
//                
//            } else {
//                // Non-synchronizing actions
//                
//                // Multiply commands with identity matrix (because of non-synchronization)
//                moduleDd1.commandsDd = moduleDd1.commandsDd * identityDd2;
//                moduleDd2.commandsDd = moduleDd2.commandsDd * identityDd1;
//                
//                // Combine modules
//                switch (generationInfo.program.getModelType()) {
//                    case storm::prism::Program::ModelType::DTMC:
//                        
//                        // No non-determinism, just sum up
//                        moduleDd.guardDd = moduleDd1.guardDd + moduleDd2.guardDd;
//                        moduleDd.commandsDd = moduleDd1.commandsDd + moduleDd2.commandsDd;
//                        moduleDd.usedNondetVariables = 0;
//                        break;
//                    case storm::prism::Program::ModelType::MDP:
//                        
//                        // Combine modules and solve non-determinism
//                        moduleDd = combineModulesMDP(generationInfo, moduleDd1, moduleDd2);
//                        break;
//                    default:
//                        STORM_LOG_ASSERT(false, "Illegal model type.");
//                }
//            }
//            
//            return moduleDd;
//        }
//        
//        template <storm::dd::DdType Type>
//        ModuleDecisionDiagram<Type> SymbolicModelAdapter<Type>::combineModulesMDP(GenerationInformation const & generationInfo, ModuleDecisionDiagram<Type> moduleDd1, ModuleDecisionDiagram<Type> moduleDd2){
//            
//            // Module DD
//            ModuleDecisionDiagram<Type> moduleDd = ModuleDecisionDiagram<Type>();
//            
//            storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//            storm::dd::Dd<Type> variableDd = generationInfo.manager->getZero();
//            
//            // Check if one command DD equals 0
//            if (moduleDd1.commandsDd.isZero()) {
//                moduleDd.guardDd = moduleDd2.guardDd;
//                moduleDd.commandsDd = moduleDd2.commandsDd;
//                moduleDd.usedNondetVariables = moduleDd2.usedNondetVariables;
//                return moduleDd;
//            }
//            if (moduleDd2.commandsDd.isZero()) {
//                moduleDd.guardDd = moduleDd1.guardDd;
//                moduleDd.commandsDd = moduleDd1.commandsDd;
//                moduleDd.usedNondetVariables = moduleDd1.usedNondetVariables;
//                return moduleDd;
//            }
//            
//            // Solve non-determinism
//            
//            // Check index of DD variables
//            if (moduleDd1.usedNondetVariables > moduleDd2.usedNondetVariables) {
//                temporary = generationInfo.manager->getOne();
//                
//                for (uint_fast64_t i = moduleDd2.usedNondetVariables+1; i <= moduleDd1.usedNondetVariables; ++i){
//                    // Get variable and set to 0
//                    variableDd = generationInfo.manager->getEncoding("nondet" + std::to_string(i), 0);
//                    temporary = temporary * variableDd;
//                }
//                moduleDd2.commandsDd = moduleDd2.commandsDd * temporary;
//                moduleDd2.usedNondetVariables = moduleDd1.usedNondetVariables;
//            }
//            if (moduleDd2.usedNondetVariables > moduleDd1.usedNondetVariables) {
//                temporary = generationInfo.manager->getOne();
//                
//                for (uint_fast64_t i = moduleDd1.usedNondetVariables+1; i <= moduleDd2.usedNondetVariables; ++i){
//                    // Get variable and set to 0
//                    variableDd = generationInfo.manager->getEncoding("nondet" + std::to_string(i), 0);
//                    temporary = temporary * variableDd;
//                }
//                moduleDd1.commandsDd = moduleDd1.commandsDd * temporary;
//                moduleDd1.usedNondetVariables = moduleDd2.usedNondetVariables;
//            }
//            
//            // Get new nondet. variable
//            variableDd = variableDd = generationInfo.manager->getEncoding("nondet" + std::to_string(moduleDd1.usedNondetVariables + 1), 1);
//            
//            // Set nondet. variable
//            moduleDd2.commandsDd = moduleDd2.commandsDd * variableDd;
//            moduleDd1.commandsDd = moduleDd1.commandsDd * (!variableDd);
//            
//            // Combine command DDs
//            moduleDd.commandsDd = moduleDd1.commandsDd + moduleDd2.commandsDd;
//            
//            // Combine guard DDs
//            moduleDd.guardDd = moduleDd1.guardDd || moduleDd2.guardDd;
//            
//            moduleDd.usedNondetVariables = moduleDd1.usedNondetVariables + 1;
//            
//            return moduleDd;
//        }
//        
//        template <storm::dd::DdType Type>
//        SystemComponentDecisionDiagram<Type> SymbolicModelAdapter<Type>::createSystemComponentDecisionDiagramm(GenerationInformation const & generationInfo, storm::prism::Module const& module, std::vector<uint_fast64_t> usedNondetVariablesVector){
//            
//            uint_fast64_t numberOfSynchronizingActions = generationInfo.allSynchronizingActions.size();
//            
//            // System Component DD
//            SystemComponentDecisionDiagram<Type> systemComponentDd(numberOfSynchronizingActions);
//            
//            // Create module DD for independent actions
//            systemComponentDd.independentActionDd = createModuleDecisionDiagramm(generationInfo, module, "", 0);
//            
//            // Create module DD for synchronizing actions
//            for (uint_fast64_t i = 0; i < numberOfSynchronizingActions; ++i){
//                
//                if (module.hasAction(generationInfo.allSynchronizingActions[i])){
//                    systemComponentDd.synchronizingActionDds[i] = createModuleDecisionDiagramm(generationInfo, module, generationInfo.allSynchronizingActions[i], usedNondetVariablesVector[i]);
//                }else{
//                    switch (generationInfo.program.getModelType()){
//                        case storm::prism::Program::ModelType::DTMC:
//                            systemComponentDd.synchronizingActionDds[i] = ModuleDecisionDiagram<Type>(generationInfo.manager->getZero(), generationInfo.manager->getZero(), 0);
//                            break;
//                        case storm::prism::Program::ModelType::MDP:
//                            systemComponentDd.synchronizingActionDds[i] = ModuleDecisionDiagram<Type>(generationInfo.manager->getZero(), generationInfo.manager->getZero(), usedNondetVariablesVector[i]);
//                            break;
//                        default:
//                            STORM_LOG_ASSERT(false, "Illegal model type.");
//                    }
//                }
//                
//            }
//            
//            // Get module identity matrix
//            systemComponentDd.identityMatrix = generationInfo.moduleToIdentityDecisionDiagramMap.at(module.getName());
//            
//            // Store all synchronizing actions
//            systemComponentDd.allSynchronizingActions.insert(module.getActions().begin(), module.getActions().end());
//            
//            return systemComponentDd;
//        }
//        
//        // TODO
//        std::map<std::string, int_fast64_t> getMetaVariableMapping(std::vector<std::string> metaVariables, uint_fast64_t value){
//            
//            std::map<std::string, int_fast64_t> metaVariableNameToValueMap = std::map<std::string, int_fast64_t>();
//            
//            for (uint_fast64_t i = 0; i < metaVariables.size(); ++i) {
//                if (value & (1ull << (metaVariables.size() - i - 1))) {
//                    metaVariableNameToValueMap.insert(std::make_pair(metaVariables[i], 1));
//                }
//                else {
//                    metaVariableNameToValueMap.insert(std::make_pair(metaVariables[i], 0));
//                }
//            }
//
//            return metaVariableNameToValueMap;
//        }
//        
//        template <storm::dd::DdType Type>
//        ModuleDecisionDiagram<Type> SymbolicModelAdapter<Type>::combineCommandsMDP(std::shared_ptr<storm::dd::DdManager<Type>> const & manager, uint_fast64_t numberOfCommands, std::vector<storm::dd::Dd<Type>> const& commandDds, std::vector<storm::dd::Dd<Type>> const & guardDds, uint_fast64_t usedNondetVariables){
//            
//            // Module DD
//            ModuleDecisionDiagram<Type> moduleDd = ModuleDecisionDiagram<Type>();
//            
//            // Initialize DDs
//            storm::dd::Dd<Type> guardRangeDd = manager->getZero();
//            storm::dd::Dd<Type> commandsDd = manager->getZero();
//            storm::dd::Dd<Type> temporaryDd = manager->getZero();
//            
//            // Check for overlapping guards
//            storm::dd::Dd<Type> overlappingGuardDd = manager->getZero();
//            
//            for (uint_fast64_t i = 0; i < numberOfCommands; ++i) {
//                overlappingGuardDd = overlappingGuardDd + guardDds[i];
//                guardRangeDd = guardRangeDd || guardDds[i];
//            }
//            
//            uint_fast64_t maxChoices = overlappingGuardDd.getMax();
//            
//            // Check for no choice or no non-determinism
//            if (maxChoices == 0 || maxChoices == 1) {
//                
//                if (maxChoices == 1) {
//                    // Sum up all command updates
//                    for (uint_fast64_t i = 0; i < numberOfCommands; ++i) {
//                        temporaryDd = guardDds[i] * commandDds[i];
//                        commandsDd = commandsDd + temporaryDd;
//                    }
//                }
//                
//                moduleDd.guardDd = guardRangeDd;
//                moduleDd.commandsDd = commandsDd;
//                moduleDd.usedNondetVariables = usedNondetVariables;
//                
//                return moduleDd;
//            }
//            
//            // Calculate number of required variables (log2(maxChoices))
//            uint_fast64_t numberOfBinaryVariables = static_cast<uint_fast64_t>(std::ceil(log2(maxChoices)));
//            
//            // Allocate local nondet. choice variables
//            std::vector<std::string> nondetVariables(numberOfBinaryVariables);
//            for (uint_fast64_t i = 1; i <= numberOfBinaryVariables; ++i) {
//                nondetVariables[i-1] = "nondet" + std::to_string(usedNondetVariables + i);
//            }
//            
//            // Initialize more DDs
//            storm::dd::Dd<Type> equalsNumberOfChoicesDd = manager->getZero();
//            std::vector<storm::dd::Dd<Type>> choiceDds(maxChoices);
//            std::vector<storm::dd::Dd<Type>> remainingDds(maxChoices);
//            
//            storm::dd::Dd<Type> temporaryDd1 = manager->getZero();
//            storm::dd::Dd<Type> temporaryDd2 = manager->getZero();
//            
//            for (uint_fast64_t currentChoices = 1; currentChoices <= maxChoices; ++currentChoices) {
//                
//                // Check for paths with exactly i nondet. choices
//                equalsNumberOfChoicesDd = overlappingGuardDd.equals(manager->getConstant(static_cast<double> (currentChoices)));
//                
//                if (equalsNumberOfChoicesDd.isZero()) continue;
//                
//                // Reset DDs
//                for (uint_fast64_t j = 0; j < currentChoices; ++j) {
//                    choiceDds[j] = manager->getZero();
//                    remainingDds[j] = equalsNumberOfChoicesDd;
//                }
//                
//                // Check all commands
//                for (uint_fast64_t j = 0; j < numberOfCommands; ++j) {
//                    
//                    // Check if command guard overlaps with equalsNumberOfChoicesDd
//                    temporaryDd1 = guardDds[j] && equalsNumberOfChoicesDd;
//                    if (temporaryDd1.isZero()) continue;
//                    
//                    // Split nondet. choices
//                    for (uint_fast64_t k = 0; k < currentChoices; ++k) {
//                        
//                        // Calculate maximal overlapping parts of command guard and remaining DD (for current index)
//                        temporaryDd2 = temporaryDd1 && remainingDds[k];
//                        
//                        // Check if we can add some overlapping parts to the current index
//                        if (temporaryDd2 != manager->getZero()) {
//                            
//                            // Remove overlapping parts from the remaining DD
//                            remainingDds[k] = remainingDds[k] && (!temporaryDd2);
//                            
//                            // Combine guard (overlapping parts) with command updates
//                            temporaryDd = temporaryDd2 * commandDds[j];
//                            // Add command DD to the commands with current index
//                            choiceDds[k] = choiceDds[k] + temporaryDd;
//                        }
//                        
//                        // Remove overlapping parts from the command guard DD
//                        temporaryDd1 = temporaryDd1 && (!temporaryDd2);
//                        
//                        // Check if the command guard DD is already 0
//                        if (temporaryDd1.isZero()) break;
//                    }
//                }
//                
//                // Set nondet. choices for corresponding DDs
//                for (uint_fast64_t j = 0; j < currentChoices; ++j) {
//                    
//                    temporaryDd1 = manager->getZero();
//                    
//                    // Set chosen variables to value j
//                    temporaryDd1.setValue(getMetaVariableMapping(nondetVariables, j), 1);
//                    
//                    // Multiply setting of nondet. variables with corresponding commands
//                    temporaryDd = temporaryDd1 * choiceDds[j];
//                    // Sum up all DDs (no non-determinism any more)
//                    commandsDd = commandsDd + temporaryDd;
//                }
//                
//                // Delete currentChoices out of overlapping DD
//                overlappingGuardDd = overlappingGuardDd * (!equalsNumberOfChoicesDd);
//            }
//            
//            moduleDd.guardDd = guardRangeDd;
//            moduleDd.commandsDd = commandsDd;
//            moduleDd.usedNondetVariables = usedNondetVariables + numberOfBinaryVariables;
//            
//            return moduleDd;
//        }
//        
//        template <storm::dd::DdType Type>
//        ModuleDecisionDiagram<Type> SymbolicModelAdapter<Type>::createModuleDecisionDiagramm(GenerationInformation const & generationInfo, storm::prism::Module const& module, std::string const& synchronizingAction, uint_fast64_t usedNondetVariables){
//            
//            // Module DD
//            ModuleDecisionDiagram<Type> moduleDd;
//            
//            // Set up vectors
//            uint_fast64_t numberOfCommands = module.getNumberOfCommands();
//            std::vector<storm::dd::Dd<Type>> guardDds(numberOfCommands);
//            std::vector<storm::dd::Dd<Type>> commandDds(numberOfCommands);
//            
//            for (uint_fast64_t j = 0; j < numberOfCommands; ++j) {
//                
//                storm::prism::Command const& command = module.getCommand(j);
//                
//                // Check if command action matches requested synchronizing action
//                if (synchronizingAction == command.getActionName()) {
//                    
//                    // Translate guard
//                    guardDds[j] = storm::adapters::SymbolicExpressionAdapter<Type>::translateExpression(command.getGuardExpression().getBaseExpressionPointer(), generationInfo.program, generationInfo.manager);
//
//                    if (guardDds[j].isZero()){
//                        LOG4CPLUS_WARN(logger, "A guard is unsatisfiable.");
//                    }
//                    
//                    // Create command DD
//                    commandDds[j] = createCommandDecisionDiagramm(generationInfo, module, guardDds[j], command);
//                    
//                    commandDds[j] = commandDds[j] * guardDds[j];
//                    
//                    // check negative probabilities/rates
//                    if (commandDds[j].getMin() < 0){
//                        LOG4CPLUS_WARN(logger, "Negative probabilites/rates in command " << (j + 1) << ".");
//                    }
//                }
//                else {
//                    // Otherwise use zero DDs
//                    guardDds[j] = generationInfo.manager->getZero();
//                    commandDds[j] = generationInfo.manager->getZero();
//                }
//            }
//            
//            // combine command DDs with guard DDs
//            switch (generationInfo.program.getModelType()){
//                case storm::prism::Program::ModelType::DTMC:
//                    moduleDd = combineCommandsDTMC(generationInfo.manager, numberOfCommands, commandDds, guardDds);
//                    break;
//                case storm::prism::Program::ModelType::MDP:
//                    moduleDd = combineCommandsMDP(generationInfo.manager, numberOfCommands, commandDds, guardDds, usedNondetVariables);
//                    break;
//                default:
//                    STORM_LOG_ASSERT(false, "Illegal model type.");
//            }
//            
//            return moduleDd;
//        }
//        
//        template <storm::dd::DdType Type>
//        storm::dd::Dd<Type> SymbolicModelAdapter<Type>::createCommandDecisionDiagramm(GenerationInformation const & generationInfo, storm::prism::Module const& module, storm::dd::Dd<Type> const& guard, storm::prism::Command const& command){
//            
//            // Command DD
//            storm::dd::Dd<Type> commandDd = generationInfo.manager->getZero();
//            
//            for (uint_fast64_t i = 0; i < command.getNumberOfUpdates(); ++i) {
//                
//                // Create update DD
//                storm::dd::Dd<Type> updateDd = createUpdateDecisionDiagramm(generationInfo, module, guard, command.getUpdate(i));
//                
//                if (updateDd.isZero()){
//                    LOG4CPLUS_WARN(logger, "Update " << (i + 1) << " does not do anything.");
//                }
//                
//                // Multiply likelihood expression (MDP: transition probability)
//                double p = command.getUpdate(i).getLikelihoodExpression().evaluateAsDouble();
//                
//                updateDd = updateDd * generationInfo.manager->getConstant(p);
//                
//                commandDd = commandDd + updateDd;
//            }
//            
//            return commandDd;
//        }
//
//        template <storm::dd::DdType Type>
//        storm::dd::Dd<Type> SymbolicModelAdapter<Type>::createUpdateDecisionDiagramm(GenerationInformation const & generationInfo, storm::prism::Module const& module, storm::dd::Dd<Type> const& guard, storm::prism::Update const& update){
//            
//            // Update DD
//            storm::dd::Dd<Type> updateDd = generationInfo.manager->getOne();
//            
//            // Assignments (integer and boolean)
//            std::vector<storm::prism::Assignment> assignments = update.getAssignments();
//            for (auto singleAssignment : assignments) {
//                
//                // Translate first part of assignment
//                storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//                
//                temporary = generationInfo.manager->getIdentity(singleAssignment.getVariableName() + "'");
//                
//                // Translate second part of assignment
//                storm::dd::Dd<Type> updateExpr = storm::adapters::SymbolicExpressionAdapter<Type>::translateExpression(singleAssignment.getExpression().getBaseExpressionPointer(), generationInfo.program, generationInfo.manager);
//                
//                storm::dd::Dd<Type> result = updateExpr * guard;
//                // Combine first and second part of the assignment
//                result = result.equals(temporary);
//                result = result * guard;
//                
//                // Filter range
//                result = result * generationInfo.manager->getRange(singleAssignment.getVariableName() + "'");
//                
//                updateDd = updateDd * result;
//            }
//
//            // All unused global boolean variables do not change
//            for (uint_fast64_t i = 0; i < generationInfo.program.getGlobalBooleanVariables().size(); ++i) {
//                storm::prism::BooleanVariable const& booleanVariable = generationInfo.program.getGlobalBooleanVariables().at(i);
//                if (update.getAssignmentMapping().find(booleanVariable.getName()) == update.getAssignmentMapping().end()) {
//                    // Multiply identity matrix
//                    updateDd = updateDd * generationInfo.variableToIdentityDecisionDiagramMap.at(booleanVariable.getName());
//                }
//            }
//            
//            // All unused global integer variables do not change
//            for (uint_fast64_t i = 0; i < generationInfo.program.getGlobalIntegerVariables().size(); ++i) {
//                storm::prism::IntegerVariable const& integerVariable = generationInfo.program.getGlobalIntegerVariables().at(i);
//                if (update.getAssignmentMapping().find(integerVariable.getName()) == update.getAssignmentMapping().end()) {
//                    // Multiply identity matrix
//                    updateDd = updateDd * generationInfo.variableToIdentityDecisionDiagramMap.at(integerVariable.getName());
//                }
//            }
//            
//            // All unused boolean variables do not change
//            for (uint_fast64_t i = 0; i < module.getNumberOfBooleanVariables(); ++i) {
//                storm::prism::BooleanVariable const& booleanVariable = module.getBooleanVariables().at(i);
//                if (update.getAssignmentMapping().find(booleanVariable.getName()) == update.getAssignmentMapping().end()) {
//                    // Multiply identity matrix
//                    updateDd = updateDd * generationInfo.variableToIdentityDecisionDiagramMap.at(booleanVariable.getName());
//                }
//            }
//            
//            // All unused integer variables do not change
//            for (uint_fast64_t i = 0; i < module.getNumberOfIntegerVariables(); ++i) {
//                storm::prism::IntegerVariable const& integerVariable = module.getIntegerVariables().at(i);
//                if (update.getAssignmentMapping().find(integerVariable.getName()) == update.getAssignmentMapping().end()) {
//                    // Multiply identity matrix
//                    updateDd = updateDd * generationInfo.variableToIdentityDecisionDiagramMap.at(integerVariable.getName());
//                }
//            }
//
//            return updateDd;
//        }
//        
//        template <storm::dd::DdType Type>
//        storm::dd::Dd<Type> SymbolicModelAdapter<Type>::getInitialStateDecisionDiagram(GenerationInformation const & generationInfo) {
//            storm::dd::Dd<Type> initialStates = generationInfo.manager->getOne();
//            storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//            
//            // Global Boolean variables
//            for (uint_fast64_t j = 0; j < generationInfo.program.getGlobalBooleanVariables().size(); ++j) {
//                storm::prism::BooleanVariable const& booleanVariable = generationInfo.program.getGlobalBooleanVariables().at(j);
//                temporary = generationInfo.manager->getEncoding(booleanVariable.getName(), booleanVariable.getInitialValueExpression().evaluateAsBool());
//                initialStates = initialStates * temporary;
//            }
//            
//            // Global Integer variables
//            for (uint_fast64_t j = 0; j < generationInfo.program.getGlobalIntegerVariables().size(); ++j) {
//                storm::prism::IntegerVariable const& integerVariable = generationInfo.program.getGlobalIntegerVariables().at(j);
//                temporary = generationInfo.manager->getEncoding(integerVariable.getName(), integerVariable.getInitialValueExpression().evaluateAsInt());
//                initialStates = initialStates * temporary;
//            }
//            
//            for (uint_fast64_t i = 0; i < generationInfo.program.getNumberOfModules(); ++i) {
//                storm::prism::Module const& module = generationInfo.program.getModule(i);
//                
//                // Boolean variables
//                for (uint_fast64_t j = 0; j < module.getNumberOfBooleanVariables(); ++j) {
//                    storm::prism::BooleanVariable const& booleanVariable = module.getBooleanVariables().at(j);
//                    temporary = generationInfo.manager->getEncoding(booleanVariable.getName(), booleanVariable.getInitialValueExpression().evaluateAsBool());
//                    initialStates = initialStates * temporary;
//                }
//                
//                // Integer variables
//                for (uint_fast64_t j = 0; j < module.getNumberOfIntegerVariables(); ++j) {
//                    storm::prism::IntegerVariable const& integerVariable = module.getIntegerVariables().at(j);
//                    temporary = generationInfo.manager->getEncoding(integerVariable.getName(), integerVariable.getInitialValueExpression().evaluateAsInt());
//                    initialStates = initialStates * temporary;
//                }
//            }
//            
//            return initialStates;
//        }
//        
//        template <storm::dd::DdType Type>
//        storm::dd::Dd<Type> SymbolicModelAdapter<Type>::performReachability(GenerationInformation & generationInfo, storm::dd::Dd<Type> const& systemDd, storm::dd::Dd<Type> const& initialStateDd) {
//            
//            // Initialize the clock.
//            auto clock = std::chrono::high_resolution_clock::now();
//            
//            
//            storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//            storm::dd::Dd<Type> S;
//            storm::dd::Dd<Type> U;
//            
//            // Get initial state
//            storm::dd::Dd<Type> reachableStates = initialStateDd;
//            
//            // Copy current state
//            storm::dd::Dd<Type> newReachableStates = reachableStates;
//            
//            std::set<std::string> abstractVariables = std::set<std::string>();
//            
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                for (uint_fast64_t i = 0; i < generationInfo.allSynchronizingActions.size(); ++i) {
//                    // Synchronizing variables
//                    if (systemDd.containsMetaVariable("sync" + std::to_string(i))) {
//                        abstractVariables.insert("sync" + std::to_string(i));
//                    }
//                }
//                for (uint_fast64_t i = 1; i <= generationInfo.numberOfNondetVariables; ++i) {
//                    // Nondet. variables
//                    if (systemDd.containsMetaVariable("nondet" + std::to_string(i))) {
//                        abstractVariables.insert("nondet" + std::to_string(i));
//                    }
//                }
//            }
//            
//            // Create system BDD
//            storm::dd::Dd<Type> systemBdd = systemDd.notZero();
//            
//            // For MDPs, we need to abstract from the nondeterminism variables, but we can do so prior to the
//            // reachability analysis.
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                // Abstract from synchronizing and nondet. variables (MDPs)
//                systemBdd = systemBdd.existsAbstract(abstractVariables);
//            }
//            
//            // Initialize variables and choose option
//            bool changed;
//            int iter = 0;
//            int option = storm::settings::adapterSettings().getReachabilityMethod();
//            
//            //TODO: Name reachability options.
//            std::cout << "Reachability option: " << option << std::endl;
//            
//            if (option == 3 || option == 4){
//                
//                S = storm::dd::Dd<Type>(initialStateDd);
//                U = storm::dd::Dd<Type>(initialStateDd);
//                
//                generationInfo.globalSystemDds.independentActionDd.commandsDd = generationInfo.globalSystemDds.independentActionDd.commandsDd.notZero();
//                generationInfo.globalSystemDds.independentActionDd.commandsDd = generationInfo.globalSystemDds.independentActionDd.commandsDd.existsAbstract(abstractVariables);
//                
//                for (uint_fast64_t i = 0; i < generationInfo.allSynchronizingActions.size(); ++i) {
//                    generationInfo.globalSystemDds.synchronizingActionDds[i].commandsDd = generationInfo.globalSystemDds.synchronizingActionDds[i].commandsDd.notZero();
//                    generationInfo.globalSystemDds.synchronizingActionDds[i].commandsDd = generationInfo.globalSystemDds.synchronizingActionDds[i].commandsDd.existsAbstract(abstractVariables);
//                }
//            }
//            
//            // Perform updates until nothing changes
//            do {
//                if (option == 1){
//                    iter++;
//                    changed = true;
//                    
//                    newReachableStates = newReachableStates * systemBdd;
//                    
//                    // Abstract from row meta variables
//                    newReachableStates = newReachableStates.existsAbstract(generationInfo.rowMetaVariableNames);
//                    
//                    // Swap column variables to row variables
//                    newReachableStates.swapVariables(generationInfo.metaVariablePairs);
//                    
//                    newReachableStates = newReachableStates * (!reachableStates);
//                    
//                    // Check if something has changed
//                    if (newReachableStates.isZero()) {
//                        changed = false;
//                    }
//                    
//                    // Update reachableStates DD
//                    reachableStates = reachableStates + newReachableStates;
//                    
//                } else if (option == 2) {
//                    iter++;
//                    changed = false;
//                    
//                    newReachableStates = newReachableStates * systemBdd;
//                    
//                    // Abstract from row meta variables
//                    newReachableStates = newReachableStates.existsAbstract(generationInfo.rowMetaVariableNames);
//                    
//                    // Swap column variables to row variables
//                    newReachableStates.swapVariables(generationInfo.metaVariablePairs);
//                    
//                    newReachableStates = newReachableStates || reachableStates;
//                    
//                    // Check if something has changed
//                    if (newReachableStates != reachableStates) {
//                        changed = true;
//                    }
//                    
//                    // Update reachableStates DD
//                    reachableStates = newReachableStates;
//                    
//                } else if (option == 3) {
//                    iter++;
//                    changed = true;
//                    
//                    newReachableStates = generationInfo.manager->getZero();
//                    
//                    temporary = U * generationInfo.globalSystemDds.independentActionDd.commandsDd;
//                    newReachableStates = newReachableStates.existsAbstract(generationInfo.rowMetaVariableNames);
//                    newReachableStates.swapVariables(generationInfo.metaVariablePairs);
//                    newReachableStates = temporary;
//                    
//                    for (uint_fast64_t i = 0; i < generationInfo.allSynchronizingActions.size(); ++i) {
//                        temporary = U * generationInfo.globalSystemDds.synchronizingActionDds[i].commandsDd;
//                        temporary = temporary.existsAbstract(generationInfo.rowMetaVariableNames);
//                        temporary.swapVariables(generationInfo.metaVariablePairs);
//                        newReachableStates = newReachableStates || temporary;
//                    }
//                    
//                    newReachableStates = newReachableStates.existsAbstract(generationInfo.rowMetaVariableNames);
//                    newReachableStates.swapVariables(generationInfo.metaVariablePairs);
//                    U = U || newReachableStates;
//                    
//                    if (U == S){
//                        changed = false;
//                        reachableStates = S;
//                        break;
//                    }
//                    
//                    S = S || U;
//                    
//                }
//                else if (option == 4) {
//                    iter++;
//                    changed = true;
//                    
//                    temporary = U * generationInfo.globalSystemDds.independentActionDd.commandsDd;
//                    temporary = temporary.existsAbstract(generationInfo.rowMetaVariableNames);
//                    temporary.swapVariables(generationInfo.metaVariablePairs);
//                    U = U || temporary;
//                    
//                    for (uint_fast64_t i = 0; i < generationInfo.allSynchronizingActions.size(); ++i) {
//                        temporary = U * generationInfo.globalSystemDds.synchronizingActionDds[i].commandsDd;
//                        temporary = temporary.existsAbstract(generationInfo.rowMetaVariableNames);
//                        temporary.swapVariables(generationInfo.metaVariablePairs);
//                        U = U || temporary;
//                    }
//                    
//                    U = U * (!S);
//                    
//                    S = S + U;
//                    
//                    if (U.isZero()){
//                        changed = false;
//                        reachableStates = S;
//                    }
//                    
//                }
//            } while (changed);
//            
//            std::cout << "Performed reachability (" << iter << " iterations) in " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - clock).count() << "ms." << std::endl;
//            
//            return reachableStates;
//        }
//        
//        template <storm::dd::DdType Type>
//        storm::dd::Dd<Type> SymbolicModelAdapter<Type>::findDeadlocks(GenerationInformation const & generationInfo, storm::dd::Dd<Type> systemDd, storm::dd::Dd<Type> const& reachableStatesDd) {
//            
//            // Initialize the clock.
//            auto clock = std::chrono::high_resolution_clock::now();
//            
//            storm::dd::Dd<Type> systemBdd = systemDd.notZero();
//            
//            std::set<std::string> abstractVariables = std::set<std::string>();
//            
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                for (uint_fast64_t i = 0; i < generationInfo.allSynchronizingActions.size(); ++i) {
//                    // Synchronizing variables
//                    if (systemDd.containsMetaVariable("sync" + std::to_string(i))) {
//                        abstractVariables.insert("sync" + std::to_string(i));
//                    }
//                }
//                for (uint_fast64_t i = 1; i <= generationInfo.numberOfNondetVariables; ++i) {
//                    // Nondet. variables
//                    if (systemDd.containsMetaVariable("nondet" + std::to_string(i))) {
//                        abstractVariables.insert("nondet" + std::to_string(i));
//                    }
//                }
//            }
//            
//            // Find states with at least one transition
//            systemBdd = systemBdd.existsAbstract(generationInfo.columnMetaVariableNames);
//            
//            // For MDPs, we need to abstract from the nondeterminism variables
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                // Abstract from synchronizing and nondet. variables (MDPs)
//                systemBdd = systemBdd.existsAbstract(abstractVariables);
//            }
//            
//            systemBdd = reachableStatesDd * (!systemBdd);
//            
//            std::cout << "Deadlocks: " << systemBdd.getNonZeroCount() << " fixed." << std::endl;
//            
//            // Check if there are deadlocks
//            if (!systemBdd.isZero()){
//                
//                storm::dd::Dd<Type> temporary = generationInfo.manager->getOne();
//                
//                // Get all variable identities
//                for (auto variable : generationInfo.rowMetaVariableNames) {
//                    temporary = temporary * generationInfo.variableToIdentityDecisionDiagramMap.at(variable);
//                }
//                
//                // Add synchronizing and nondet. variables to identity for MDPs (all set to 0)
//                if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                    for (uint_fast64_t i = 0; i < generationInfo.allSynchronizingActions.size(); ++i) {
//                        // Synchronizing variables
//                        if (systemDd.containsMetaVariable("sync" + std::to_string(i))) {
//                            temporary = temporary * generationInfo.manager->getEncoding("sync" + std::to_string(i),0);
//                        }
//                    }
//                    for (uint_fast64_t i = 1; i <= generationInfo.numberOfNondetVariables; ++i) {
//                        // Nondet. variables
//                        if (systemDd.containsMetaVariable("nondet" + std::to_string(i))) {
//                            temporary = temporary * generationInfo.manager->getEncoding("nondet" + std::to_string(i), 0);
//                        }
//                    }
//                }
//                
//                temporary = temporary * systemBdd;
//                
//                // Add self-loops to transition matrix
//                systemDd = systemDd + temporary;
//            }
//            
//            std::cout << "Fixed deadlocks in " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - clock).count() << "ms." << std::endl;
//            
//            return systemDd;
//        }
//        
//        template <storm::dd::DdType Type>
//        std::pair<std::vector<storm::dd::Dd<Type>>, std::vector<storm::dd::Dd<Type>>> SymbolicModelAdapter<Type>::computeRewards(GenerationInformation const & generationInfo, SystemComponentDecisionDiagram<Type> const& systemDds) {
//            
//            // Get number of reward modules and synchronizing actions
//            uint_fast64_t numberOfRewardModules = generationInfo.program.getRewardModels().size();
//            uint_fast64_t numberOfSynchronizingActions = generationInfo.allSynchronizingActions.size();
//            
//            // State reward DD
//            std::vector<storm::dd::Dd<Type>> stateRewardsDds = std::vector<storm::dd::Dd<Type>>(numberOfRewardModules);
//            // Transition reward DD
//            std::vector<storm::dd::Dd<Type>> transitionRewardsDds = std::vector<storm::dd::Dd<Type>>(numberOfRewardModules);
//            
//            // Initialize DDs
//            for (uint_fast64_t i = 0; i < numberOfRewardModules; ++i) {
//                stateRewardsDds[i] = generationInfo.manager->getConstant(0);
//                transitionRewardsDds[i] = generationInfo.manager->getConstant(0);
//            }
//            
//            storm::dd::Dd<Type> statesDd = generationInfo.manager->getZero();
//            storm::dd::Dd<Type> rewardsDd = generationInfo.manager->getZero();
//            storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//            
//            // Loop through all reward models
//            for (uint_fast64_t i = 0; i < numberOfRewardModules; ++i) {
//                storm::prism::RewardModel const& currentRewardModule = generationInfo.program.getRewardModels().at(i);
//                
//                // State rewards 
//                for (auto stateReward : currentRewardModule.getStateRewards()) {
//                    
//                    // Translate state and reward expression
//                    statesDd = storm::adapters::SymbolicExpressionAdapter<Type>::translateExpression(stateReward.getStatePredicateExpression().getBaseExpressionPointer(), generationInfo.program, generationInfo.manager);
//                    rewardsDd = storm::adapters::SymbolicExpressionAdapter<Type>::translateExpression(stateReward.getRewardValueExpression().getBaseExpressionPointer(), generationInfo.program, generationInfo.manager);
//                    
//                    // Restrict rewards to states
//                    temporary = statesDd * rewardsDd;
//                    
//                    // Check for negative rewards
//                    if (temporary.getMin() < 0){
//                        LOG4CPLUS_WARN(logger, "Negative state reward in reward model " << (i + 1) << ".");
//                    }
//                    
//                    if(temporary.isZero()) {
//                        LOG4CPLUS_WARN(logger, "Only zero rewards in reward model " << (i + 1) << ".");
//                    }
//                    
//                    // Combine all rewards
//                    stateRewardsDds[i] = stateRewardsDds[i] + temporary;
//                }
//                
//                // Transition rewards 
//                for (auto transitionReward : currentRewardModule.getTransitionRewards()) {
//                    
//                    // Translate state and reward expression
//                    statesDd = storm::adapters::SymbolicExpressionAdapter<Type>::translateExpression(transitionReward.getStatePredicateExpression().getBaseExpressionPointer(), generationInfo.program, generationInfo.manager);
//                    rewardsDd = storm::adapters::SymbolicExpressionAdapter<Type>::translateExpression(transitionReward.getRewardValueExpression().getBaseExpressionPointer(), generationInfo.program, generationInfo.manager);
//                    
//                    // Get action name of the transition
//                    std::string const& rewardAction = transitionReward.getActionName();
//                    
//                    if (rewardAction == "") {
//                        // Take independent action module
//                        temporary = systemDds.independentActionDd.commandsDd;
//                    }else {
//                        // Get module corresponding to the reward action
//                        for (uint_fast64_t j = 0; j < numberOfSynchronizingActions; ++j) {
//                            if (generationInfo.allSynchronizingActions[j] == rewardAction) {
//                                temporary = systemDds.synchronizingActionDds[j].commandsDd;
//                                break;
//                            }
//                        }
//                    }
//                    
//                    // Convert to BDD for MDPs (DTMC need exact values for scaling)
//                    if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) {
//                        temporary = temporary.notZero();
//                    }
//                    
//                    // Restrict to states and multiply reward values
//                    temporary = temporary * statesDd;
//                    temporary = temporary * rewardsDd;
//                    
//                    // Check for negative rewards
//                    if (temporary.getMin() < 0){
//                        LOG4CPLUS_WARN(logger, "Negative transition reward in reward model " << (i + 1) << ".");
//                    }
//                    
//                    // Combine all rewards
//                    transitionRewardsDds[i] = transitionRewardsDds[i] + temporary;
//                }
//            }
//            
//            // Scale transition rewards for DTMCs
//            if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::DTMC) {
//                for (uint_fast64_t i = 0; i < generationInfo.program.getRewardModels().size(); ++i){
//                    // Divide transition rewards through transition matrix
//                    transitionRewardsDds[i] = transitionRewardsDds[i] / systemDds.independentActionDd.commandsDd;
//                }
//            }
//            
//            // Pair to store state and transition rewards
//            return std::make_pair(stateRewardsDds, transitionRewardsDds);
//        }
//        
//        template <storm::dd::DdType Type>
//        storm::dd::Dd<Type> SymbolicModelAdapter<Type>::computeTransitionAction(GenerationInformation const & generationInfo, SystemComponentDecisionDiagram<Type> const& systemDds){
//            
//            // Transition actions DD
//            storm::dd::Dd<Type> transitionActionDd = generationInfo.manager->getZero();
//            storm::dd::Dd<Type> temporary = generationInfo.manager->getZero();
//            
//            // Store action labels for each transition (0 iff no action/tau/epsilon)
//            storm::dd::Dd<Type> commandsBdd = generationInfo.manager->getZero();
//            for (uint_fast64_t i = 0; i < generationInfo.allSynchronizingActions.size(); ++i){
//                // Get action transition matrix as BDD
//                commandsBdd = systemDds.synchronizingActionDds[i].commandsDd.notZero();
//                commandsBdd = commandsBdd.existsAbstract(generationInfo.columnMetaVariableNames);
//                
//                // Add action index
//                temporary = commandsBdd * generationInfo.manager->getConstant(i + 1);
//                transitionActionDd = transitionActionDd + temporary;
//            }
//            
//            return transitionActionDd;
//        }
        
        // Explicitly instantiate the symbolic expression adapter
        template class DdPrismModelBuilder<storm::dd::DdType::CUDD>;
        
    } // namespace adapters
} // namespace storm