From 8c1870eb549918de4ff2becf67b3c6c40efe9022 Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 18 Feb 2015 17:08:33 +0100 Subject: [PATCH] Intermediate commit. Former-commit-id: e5f251718f0f024c41d8268cf55d3eecc538f0ce --- src/builder/DdPrismModelBuilder.cpp | 175 +++++++++++++----- src/builder/DdPrismModelBuilder.h | 16 +- src/builder/ExplicitPrismModelBuilder.cpp | 6 + src/parser/PrismParser.cpp | 2 +- src/storage/expressions/Expression.cpp | 7 + src/storage/expressions/Expression.h | 8 + src/storage/prism/Command.cpp | 15 ++ src/storage/prism/Command.h | 9 + src/storage/prism/Module.cpp | 25 +++ src/storage/prism/Module.h | 8 + src/storage/prism/Program.cpp | 84 ++++++++- src/storage/prism/Program.h | 20 ++ src/storage/prism/RewardModel.cpp | 14 ++ src/storage/prism/RewardModel.h | 9 + src/utility/cli.h | 3 + .../builder/DdPrismModelBuilderTest.cpp | 27 +++ .../builder/ExplicitPrismModelBuilderTest.cpp | 28 +++ test/functional/builder/coin2-2.nm | 60 ++++++ test/functional/builder/csma2-2.nm | 130 +++++++++++++ test/functional/builder/firewire3-0.5.nm | 170 +++++++++++++++++ test/functional/builder/leader3.nm | 91 +++++++++ test/functional/builder/two_dice.nm | 40 ++++ wlan0_collide.nm | 13 ++ 23 files changed, 904 insertions(+), 56 deletions(-) create mode 100644 test/functional/builder/coin2-2.nm create mode 100644 test/functional/builder/csma2-2.nm create mode 100644 test/functional/builder/firewire3-0.5.nm create mode 100644 test/functional/builder/leader3.nm create mode 100644 test/functional/builder/two_dice.nm create mode 100644 wlan0_collide.nm diff --git a/src/builder/DdPrismModelBuilder.cpp b/src/builder/DdPrismModelBuilder.cpp index acc7c5235..43f051eb5 100644 --- a/src/builder/DdPrismModelBuilder.cpp +++ b/src/builder/DdPrismModelBuilder.cpp @@ -82,23 +82,25 @@ namespace storm { 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()); -// } -// } + // This works under the assumption that global variables are only written in non-synchronzing commands, but + // is not checked here. + for (auto const& booleanVariable : generationInfo.program.getGlobalBooleanVariables()) { + 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 unused global integer variables do not change + for (auto const& integerVariable : generationInfo.program.getGlobalIntegerVariables()) { + 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()); + } + } // All unassigned boolean variables need to keep their value. for (storm::prism::BooleanVariable const& booleanVariable : module.getBooleanVariables()) { @@ -140,6 +142,7 @@ namespace storm { commandDd += updateDd; } + (guardDd * commandDd).exportToDot(module.getName() + "_" + command.getActionName() + ".dot"); return ActionDecisionDiagram(guardDd, guardDd * commandDd); } else { return ActionDecisionDiagram(*generationInfo.manager); @@ -152,16 +155,28 @@ namespace storm { 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()); + bool relevant = (!synchronizationActionIndex && !command.isLabeled()) || (synchronizationActionIndex && command.isLabeled() && command.getActionIndex() == synchronizationActionIndex.get()); if (!relevant) { continue; } + if (synchronizationActionIndex) { + std::cout << command << " is relevant for synch " << synchronizationActionIndex.get() << std::endl; + } + // At this point, the command is known to be relevant for the action. commandDds.push_back(createCommandDecisionDiagram(generationInfo, module, command)); } + if (synchronizationActionIndex && synchronizationActionIndex.get() == 0) { + int i = 0; + for (auto const& commandDd : commandDds) { + commandDd.transitionsDd.exportToDot("cmd_" + std::to_string(i) + ".dot"); + ++i; + } + } + ActionDecisionDiagram result(*generationInfo.manager); if (!commandDds.empty()) { switch (generationInfo.program.getModelType()){ @@ -318,6 +333,9 @@ namespace storm { storm::dd::Dd action1Extended = action1.transitionsDd * identityDd2; storm::dd::Dd action2Extended = action2.transitionsDd * identityDd1; + action1.transitionsDd.exportToDot("act1.dot"); + action2.transitionsDd.exportToDot("act2.dot"); + 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) { @@ -329,24 +347,28 @@ namespace storm { // Bring both choices to the same number of variables that encode the nondeterminism. uint_fast64_t numberOfUsedNondeterminismVariables = std::max(action1.numberOfUsedNondeterminismVariables, action2.numberOfUsedNondeterminismVariables); + std::cout << "max used nondet: " << numberOfUsedNondeterminismVariables << std::endl; if (action1.numberOfUsedNondeterminismVariables > action2.numberOfUsedNondeterminismVariables) { storm::dd::Dd nondeterminisimEncoding = generationInfo.manager->getOne(); - for (uint_fast64_t i = action2.numberOfUsedNondeterminismVariables + 1; i <= action1.numberOfUsedNondeterminismVariables; ++i) { + for (uint_fast64_t i = action2.numberOfUsedNondeterminismVariables; i < action1.numberOfUsedNondeterminismVariables; ++i) { nondeterminisimEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0); } action2Extended *= nondeterminisimEncoding; } else if (action2.numberOfUsedNondeterminismVariables > action1.numberOfUsedNondeterminismVariables) { storm::dd::Dd nondeterminisimEncoding = generationInfo.manager->getOne(); - for (uint_fast64_t i = action1.numberOfUsedNondeterminismVariables + 1; i <= action2.numberOfUsedNondeterminismVariables; ++i) { + for (uint_fast64_t i = action1.numberOfUsedNondeterminismVariables; i < action2.numberOfUsedNondeterminismVariables; ++i) { nondeterminisimEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0); } action1Extended *= nondeterminisimEncoding; } + action1Extended.exportToDot("act1ext.dot"); + action2Extended.exportToDot("act2ext.dot"); + // Add a new variable that resolves the nondeterminism between the two choices. - storm::dd::Dd combinedTransitions = generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[numberOfUsedNondeterminismVariables + 1], 1).ite(action2Extended, action1Extended); + storm::dd::Dd combinedTransitions = generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[numberOfUsedNondeterminismVariables], 1).ite(action2Extended, action1Extended); return ActionDecisionDiagram(action1.guardDd || action2.guardDd, combinedTransitions, numberOfUsedNondeterminismVariables + 1); } else { @@ -358,15 +380,18 @@ namespace storm { typename DdPrismModelBuilder::ModuleDecisionDiagram DdPrismModelBuilder::createModuleDecisionDiagram(GenerationInformation& generationInfo, storm::prism::Module const& module, std::map const& synchronizingActionToOffsetMap) { // Start by creating the action DD for the independent action. ActionDecisionDiagram independentActionDd = createActionDecisionDiagram(generationInfo, module, boost::optional(), 0); + uint_fast64_t numberOfUsedNondeterminismVariables = independentActionDd.numberOfUsedNondeterminismVariables; // Create module DD for all synchronizing actions of the module. std::map 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))); + ActionDecisionDiagram tmp = createActionDecisionDiagram(generationInfo, module, actionIndex, synchronizingActionToOffsetMap.at(actionIndex)); + numberOfUsedNondeterminismVariables = std::max(numberOfUsedNondeterminismVariables, tmp.numberOfUsedNondeterminismVariables); + actionIndexToDdMap.emplace(actionIndex, tmp); } - return ModuleDecisionDiagram(independentActionDd, actionIndexToDdMap, generationInfo.moduleToIdentityMap.at(module.getName())); + return ModuleDecisionDiagram(independentActionDd, actionIndexToDdMap, generationInfo.moduleToIdentityMap.at(module.getName()), numberOfUsedNondeterminismVariables); } template @@ -375,17 +400,15 @@ namespace storm { if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) { storm::dd::Dd 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); - } + // First, determine the highest number of nondeterminism variables that is used in any action and make + // all actions use the same amout of nondeterminism variables. + uint_fast64_t numberOfUsedNondeterminismVariables = module.numberOfUsedNondeterminismVariables; + std::cout << "pumping number of used nondet variables to " << numberOfUsedNondeterminismVariables << std::endl; - // Now make all actions use the same amount of nondeterminism variables. - // Add variables to independent action DD. storm::dd::Dd nondeterminismEncoding = generationInfo.manager->getOne(); - for (uint_fast64_t i = module.independentAction.numberOfUsedNondeterminismVariables + 1; i <= numberOfUsedNondeterminismVariables; ++i) { + for (uint_fast64_t i = module.independentAction.numberOfUsedNondeterminismVariables; i < numberOfUsedNondeterminismVariables; ++i) { + std::cout << "adding " << i << " to independent" << std::endl; nondeterminismEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0); } result = module.independentAction.transitionsDd * nondeterminismEncoding; @@ -394,7 +417,7 @@ namespace storm { std::map> synchronizingActionToDdMap; for (auto const& synchronizingAction : module.synchronizingActionToDecisionDiagramMap) { nondeterminismEncoding = generationInfo.manager->getOne(); - for (uint_fast64_t i = synchronizingAction.second.numberOfUsedNondeterminismVariables + 1; i <= numberOfUsedNondeterminismVariables; ++i) { + for (uint_fast64_t i = synchronizingAction.second.numberOfUsedNondeterminismVariables; i < numberOfUsedNondeterminismVariables; ++i) { nondeterminismEncoding *= generationInfo.manager->getEncoding(generationInfo.nondeterminismMetaVariables[i], 0); } synchronizingActionToDdMap.emplace(synchronizingAction.first, synchronizingAction.second.transitionsDd * nondeterminismEncoding); @@ -422,8 +445,9 @@ namespace storm { } // Now, we can simply add all synchronizing actions to the result. - for (auto const& synchronizingAction : module.synchronizingActionToDecisionDiagramMap) { - result += synchronizingAction.second.transitionsDd; + for (auto const& synchronizingAction : synchronizingActionToDdMap) { + synchronizingAction.second.exportToDot("synch" + std::to_string(synchronizingAction.first) + ".dot"); + result += synchronizingAction.second; } return result; @@ -466,7 +490,9 @@ namespace storm { ModuleDecisionDiagram nextModule = createModuleDecisionDiagram(generationInfo, currentModule, synchronizingActionToOffsetMap); // Combine the non-synchronizing action. + uint_fast64_t numberOfUsedNondeterminismVariables = nextModule.numberOfUsedNondeterminismVariables; system.independentAction = combineUnsynchronizedActions(generationInfo, system.independentAction, nextModule.independentAction, system.identity, nextModule.identity); + numberOfUsedNondeterminismVariables = std::max(numberOfUsedNondeterminismVariables, system.independentAction.numberOfUsedNondeterminismVariables); // For all synchronizing actions that the next module does not have, we need to multiply the identity of the next module. for (auto& action : system.synchronizingActionToDecisionDiagramMap) { @@ -479,13 +505,19 @@ namespace storm { for (auto const& actionIndex : currentModule.getActionIndices()) { if (system.hasSynchronizingAction(actionIndex)) { system.synchronizingActionToDecisionDiagramMap[actionIndex] = combineSynchronizingActions(generationInfo, system.synchronizingActionToDecisionDiagramMap[actionIndex], nextModule.synchronizingActionToDecisionDiagramMap[actionIndex]); + numberOfUsedNondeterminismVariables = std::max(numberOfUsedNondeterminismVariables, system.synchronizingActionToDecisionDiagramMap[actionIndex].numberOfUsedNondeterminismVariables); } else { system.synchronizingActionToDecisionDiagramMap[actionIndex] = combineUnsynchronizedActions(generationInfo, ActionDecisionDiagram(*generationInfo.manager), nextModule.synchronizingActionToDecisionDiagramMap[actionIndex], system.identity, nextModule.identity); + numberOfUsedNondeterminismVariables = std::max(numberOfUsedNondeterminismVariables, system.synchronizingActionToDecisionDiagramMap[actionIndex].numberOfUsedNondeterminismVariables); } } // Combine identity matrices. system.identity = system.identity * nextModule.identity; + + // Keep track of the number of nondeterminism variables used. + std::cout << "num used: " << numberOfUsedNondeterminismVariables << std::endl; + system.numberOfUsedNondeterminismVariables = std::max(system.numberOfUsedNondeterminismVariables, numberOfUsedNondeterminismVariables); } storm::dd::Dd result = createSystemFromModule(generationInfo, system); @@ -493,6 +525,15 @@ namespace storm { // 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); + } else if (generationInfo.program.getModelType() == storm::prism::Program::ModelType::MDP) { + // For MDPs, we need to throw away the nondeterminism variables from the generation information that + // were never used. + std::cout << "System uses " << system.numberOfUsedNondeterminismVariables << "nd vars" << std::endl; + for (uint_fast64_t index = system.numberOfUsedNondeterminismVariables; index < generationInfo.nondeterminismMetaVariables.size(); ++index) { + std::cout << "removing " << generationInfo.nondeterminismMetaVariables[index].getName() << std::endl; + generationInfo.allNondeterminismVariables.erase(generationInfo.nondeterminismMetaVariables[index]); + } + generationInfo.nondeterminismMetaVariables.resize(system.numberOfUsedNondeterminismVariables); } return std::make_pair(result, system); @@ -508,9 +549,24 @@ namespace storm { preparedProgram = program; } - preparedProgram = preparedProgram.substituteConstants(); + if (preparedProgram.hasUndefinedConstants()) { + std::vector> undefinedConstants = preparedProgram.getUndefinedConstants(); + std::stringstream stream; + bool printComma = false; + for (auto const& constant : undefinedConstants) { + if (printComma) { + stream << ", "; + } else { + printComma = true; + } + stream << constant.get().getName() << " (" << constant.get().getType() << ")"; + } + stream << "."; + STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Program still contains these undefined constants: " + stream.str()); + } -// std::cout << "translated prog: " << preparedProgram << std::endl; + 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. @@ -519,33 +575,55 @@ namespace storm { auto clock = std::chrono::high_resolution_clock::now(); std::pair, ModuleDecisionDiagram> transitionMatrixModulePair = createSystemDecisionDiagram(generationInfo); storm::dd::Dd transitionMatrix = transitionMatrixModulePair.first; + transitionMatrix.exportToDot("trans.dot"); ModuleDecisionDiagram const& globalModule = transitionMatrixModulePair.second; // Cut the transition matrix to the reachable fragment of the state space. storm::dd::Dd reachableStates = computeReachableStates(generationInfo, createInitialStatesDecisionDiagram(generationInfo), transitionMatrix); transitionMatrix *= reachableStates; + reachableStates.exportToDot("reach.dot"); // Detect deadlocks and 1) fix them if requested 2) throw an error otherwise. storm::dd::Dd statesWithTransition = transitionMatrix.notZero(); if (program.getModelType() == storm::prism::Program::ModelType::MDP) { - statesWithTransition.existsAbstract(generationInfo.allNondeterminismVariables); + statesWithTransition = statesWithTransition.existsAbstract(generationInfo.allNondeterminismVariables); + statesWithTransition.exportToDot("after_exists.dot"); } statesWithTransition = statesWithTransition.existsAbstract(generationInfo.columnMetaVariables); storm::dd::Dd deadlockStates = reachableStates * !statesWithTransition; + deadlockStates.exportToDot("deadlocks.dot"); - if (!deadlockStates.isZero()) { - // If we need to fix deadlocks, we do so now. - if (!storm::settings::generalSettings().isDontFixDeadlocksSet()) { - STORM_LOG_WARN("Fixing deadlocks in " << deadlockStates.getNonZeroCount() << " states."); - - // FIXME: For MDPs, we need to use an action encoding if we do not want to attach a lot of self-loops. - transitionMatrix += deadlockStates * globalModule.identity; - } else { - STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The model contains " << deadlockStates.getNonZeroCount() << " deadlock states. Please unset the option to not fix deadlocks, if you want to fix them automatically."); - } +// if (!deadlockStates.isZero()) { +// // If we need to fix deadlocks, we do so now. +// if (!storm::settings::generalSettings().isDontFixDeadlocksSet()) { +// std::cout << "fixing " << deadlockStates.getNonZeroCount() << std::endl; +// STORM_LOG_WARN("Fixing deadlocks in " << deadlockStates.getNonZeroCount() << " states."); +// +// if (program.getModelType() == storm::prism::Program::ModelType::DTMC) { +// // For DTMCs, we can simply add the identity of the global module for all deadlock states. +// transitionMatrix += deadlockStates * globalModule.identity; +// } else if (program.getModelType() == storm::prism::Program::ModelType::MDP) { +// // For MDPs, however, we need to select an action associated with the self-loop, if we do not +// // want to attach a lot of self-loops to the deadlock states. +// storm::dd::Dd action = generationInfo.manager->getOne(); +// std::for_each(generationInfo.allNondeterminismVariables.begin(), generationInfo.allNondeterminismVariables.end(), [&action,&generationInfo] (storm::expressions::Variable const& metaVariable) { action *= !generationInfo.manager->getIdentity(metaVariable); } ); +// transitionMatrix += deadlockStates * globalModule.identity * action; +// (deadlockStates * globalModule.identity * action).exportToDot("selfloops.dot"); +// } +// } else { +// STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The model contains " << deadlockStates.getNonZeroCount() << " deadlock states. Please unset the option to not fix deadlocks, if you want to fix them automatically."); +// } +// } else { +// std::cout << "no deadlocks" << std::endl; +// } + + transitionMatrix.exportToDot("trans_reach.dot"); + for (auto const& var : transitionMatrix.getContainedMetaVariables()) { + std::cout << "var: " << var.getName() << std::endl; } - return std::make_pair(reachableStates, transitionMatrix * reachableStates); + std::cout << reachableStates.getNonZeroCount() << " states and " << transitionMatrix.getNonZeroCount() << " transitions." << std::endl; + return std::make_pair(reachableStates, transitionMatrix); } template @@ -569,6 +647,7 @@ namespace storm { transitionBdd = transitionBdd.existsAbstract(generationInfo.allNondeterminismVariables); } + transitionBdd.exportToDot("trans01.dot"); // Perform the BFS to discover all reachable states. bool changed = true; diff --git a/src/builder/DdPrismModelBuilder.h b/src/builder/DdPrismModelBuilder.h index 05f380060..20d94cf45 100644 --- a/src/builder/DdPrismModelBuilder.h +++ b/src/builder/DdPrismModelBuilder.h @@ -91,15 +91,15 @@ namespace storm { // This structure holds all decision diagrams related to a module. struct ModuleDecisionDiagram { - ModuleDecisionDiagram() : independentAction(), synchronizingActionToDecisionDiagramMap(), identity() { + ModuleDecisionDiagram() : independentAction(), synchronizingActionToDecisionDiagramMap(), identity(), numberOfUsedNondeterminismVariables(0) { // Intentionally left empty. } - ModuleDecisionDiagram(storm::dd::DdManager const& manager) : independentAction(manager), synchronizingActionToDecisionDiagramMap(), identity(manager.getZero()) { + ModuleDecisionDiagram(storm::dd::DdManager const& manager) : independentAction(manager), synchronizingActionToDecisionDiagramMap(), identity(manager.getZero()), numberOfUsedNondeterminismVariables(0) { // Intentionally left empty. } - ModuleDecisionDiagram(ActionDecisionDiagram const& independentAction, std::map const& synchronizingActionToDecisionDiagramMap, storm::dd::Dd const& identity) : independentAction(independentAction), synchronizingActionToDecisionDiagramMap(synchronizingActionToDecisionDiagramMap), identity(identity) { + ModuleDecisionDiagram(ActionDecisionDiagram const& independentAction, std::map const& synchronizingActionToDecisionDiagramMap, storm::dd::Dd const& identity, uint_fast64_t numberOfUsedNondeterminismVariables = 0) : independentAction(independentAction), synchronizingActionToDecisionDiagramMap(synchronizingActionToDecisionDiagramMap), identity(identity), numberOfUsedNondeterminismVariables(numberOfUsedNondeterminismVariables) { // Intentionally left empty. } @@ -118,6 +118,9 @@ namespace storm { // A decision diagram that represents the identity of this module. storm::dd::Dd identity; + + // The number of variables encoding the nondeterminism that were actually used. + uint_fast64_t numberOfUsedNondeterminismVariables; }; /*! @@ -177,8 +180,8 @@ namespace storm { */ void createMetaVariablesAndIdentities() { // Add synchronization variables. - for (uint_fast64_t i = 0; i < program.getActionIndices().size(); ++i) { - std::pair variablePair = manager->addMetaVariable("sync" + std::to_string(i)); + for (auto const& actionIndex : program.getActionIndices()) { + std::pair variablePair = manager->addMetaVariable(program.getActionName(actionIndex)); synchronizationMetaVariables.push_back(variablePair.first); allNondeterminismVariables.insert(variablePair.first); } @@ -209,7 +212,8 @@ namespace storm { storm::dd::Dd variableIdentity = manager->getIdentity(variablePair.first).equals(manager->getIdentity(variablePair.second)) * manager->getRange(variablePair.first); variableToIdentityMap.emplace(integerVariable.getExpressionVariable(), variableIdentity); rowColumnMetaVariablePairs.push_back(variablePair); - } for (storm::prism::BooleanVariable const& booleanVariable : program.getGlobalBooleanVariables()) { + } + for (storm::prism::BooleanVariable const& booleanVariable : program.getGlobalBooleanVariables()) { std::pair variablePair = manager->addMetaVariable(booleanVariable.getName()); rowMetaVariables.insert(variablePair.first); diff --git a/src/builder/ExplicitPrismModelBuilder.cpp b/src/builder/ExplicitPrismModelBuilder.cpp index 974d9d306..153ab0afe 100644 --- a/src/builder/ExplicitPrismModelBuilder.cpp +++ b/src/builder/ExplicitPrismModelBuilder.cpp @@ -103,6 +103,8 @@ namespace storm { // If the program still contains undefined constants and we are not in a parametric setting, assemble an appropriate error message. #ifdef STORM_HAVE_CARL + // If the program either has undefined constants or we are building a parametric model, but the parameters + // not only appear in the probabilities, we re if (!std::is_same::value && preparedProgram.hasUndefinedConstants()) { #else if (preparedProgram.hasUndefinedConstants()) { @@ -120,6 +122,10 @@ namespace storm { } stream << "."; STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Program still contains these undefined constants: " + stream.str()); +#ifdef STORM_HAVE_CARL + } else if (std::is_same::value && !preparedProgram.hasUndefinedConstantsOnlyInUpdateProbabilitiesAndRewards()) { + STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The program contains undefined constants that appear in some places other than update probabilities and reward value expressions, which is not admitted."); +#endif } // Now that we have defined all the constants in the program, we need to substitute their appearances in diff --git a/src/parser/PrismParser.cpp b/src/parser/PrismParser.cpp index 3db49eb16..a96a5cd66 100644 --- a/src/parser/PrismParser.cpp +++ b/src/parser/PrismParser.cpp @@ -142,7 +142,7 @@ namespace storm { commandDefinition = (qi::lit("[") > -(identifier[qi::_a = qi::_1]) > qi::lit("]") > +(qi::char_ - qi::lit(";")) > qi::lit(";"))[qi::_val = phoenix::bind(&PrismParser::createCommand, phoenix::ref(*this), qi::_a, qi::_r1)]; commandDefinition.name("command definition"); - moduleDefinition = ((qi::lit("module") >> identifier >> *(variableDefinition(qi::_a, qi::_b))) > +commandDefinition(qi::_r1) > qi::lit("endmodule"))[qi::_val = phoenix::bind(&PrismParser::createModule, phoenix::ref(*this), qi::_1, qi::_a, qi::_b, qi::_2, qi::_r1)]; + moduleDefinition = ((qi::lit("module") >> identifier >> *(variableDefinition(qi::_a, qi::_b))) > *commandDefinition(qi::_r1) > qi::lit("endmodule"))[qi::_val = phoenix::bind(&PrismParser::createModule, phoenix::ref(*this), qi::_1, qi::_a, qi::_b, qi::_2, qi::_r1)]; moduleDefinition.name("module definition"); moduleRenaming = ((qi::lit("module") >> identifier >> qi::lit("=")) > identifier > qi::lit("[") diff --git a/src/storage/expressions/Expression.cpp b/src/storage/expressions/Expression.cpp index 3b79861d5..630e17a8a 100644 --- a/src/storage/expressions/Expression.cpp +++ b/src/storage/expressions/Expression.cpp @@ -100,6 +100,13 @@ namespace storm { return result; } + bool Expression::containsVariable(std::set const& variables) const { + std::set appearingVariables = this->getVariables(); + std::set intersection; + std::set_intersection(variables.begin(), variables.end(), appearingVariables.begin(), appearingVariables.end(), std::inserter(intersection, intersection.begin())); + return !intersection.empty(); + } + bool Expression::isRelationalExpression() const { if (!this->isFunctionApplication()) { return false; diff --git a/src/storage/expressions/Expression.h b/src/storage/expressions/Expression.h index 205012331..afc04bf56 100644 --- a/src/storage/expressions/Expression.h +++ b/src/storage/expressions/Expression.h @@ -213,6 +213,14 @@ namespace storm { */ std::set getVariables() const; + /*! + * Retrieves whether the expression contains any of the given variables. + * + * @param variables The variables to search for. + * @return True iff any of the variables appear in the expression. + */ + bool containsVariable(std::set const& variables) const; + /*! * Retrieves the base expression underlying this expression object. Note that prior to calling this, the * expression object must be properly initialized. diff --git a/src/storage/prism/Command.cpp b/src/storage/prism/Command.cpp index a476e2fd9..7141ed455 100644 --- a/src/storage/prism/Command.cpp +++ b/src/storage/prism/Command.cpp @@ -48,6 +48,21 @@ namespace storm { return labeled; } + bool Command::containsVariablesOnlyInUpdateProbabilities(std::set const& undefinedConstantVariables) const { + if (this->getGuardExpression().containsVariable(undefinedConstantVariables)) { + return false; + } + for (auto const& update : this->getUpdates()) { + for (auto const& assignment : update.getAssignments()) { + if (assignment.getExpression().containsVariable(undefinedConstantVariables)) { + return false; + } + } + } + + return true; + } + std::ostream& operator<<(std::ostream& stream, Command const& command) { stream << "[" << command.getActionName() << "] " << command.getGuardExpression() << " -> "; for (uint_fast64_t i = 0; i < command.getUpdates().size(); ++i) { diff --git a/src/storage/prism/Command.h b/src/storage/prism/Command.h index e0dac47f9..b983a1ca9 100644 --- a/src/storage/prism/Command.h +++ b/src/storage/prism/Command.h @@ -99,6 +99,15 @@ namespace storm { */ bool isLabeled() const; + /*! + * Checks whether the given set of variables only appears in the update probabilities of the command. + * + * @param undefinedConstantVariables The set of variables that may only appear in the update probabilities + * of the command. + * @return True iff the given set of variables only appears in the update probabilities of the command. + */ + bool containsVariablesOnlyInUpdateProbabilities(std::set const& undefinedConstantVariables) const; + friend std::ostream& operator<<(std::ostream& stream, Command const& command); private: diff --git a/src/storage/prism/Module.cpp b/src/storage/prism/Module.cpp index e6adf07b1..00b3e2828 100644 --- a/src/storage/prism/Module.cpp +++ b/src/storage/prism/Module.cpp @@ -170,6 +170,31 @@ namespace storm { return Module(this->getName(), newBooleanVariables, newIntegerVariables, newCommands, this->getFilename(), this->getLineNumber()); } + bool Module::containsVariablesOnlyInUpdateProbabilities(std::set const& undefinedConstantVariables) const { + for (auto const& booleanVariable : this->getBooleanVariables()) { + if (booleanVariable.getInitialValueExpression().containsVariable(undefinedConstantVariables)) { + return false; + } + } + for (auto const& integerVariable : this->getIntegerVariables()) { + if (integerVariable.getInitialValueExpression().containsVariable(undefinedConstantVariables)) { + return false; + } + if (integerVariable.getLowerBoundExpression().containsVariable(undefinedConstantVariables)) { + return false; + } + if (integerVariable.getUpperBoundExpression().containsVariable(undefinedConstantVariables)) { + return false; + } + } + + for (auto const& command : this->getCommands()) { + command.containsVariablesOnlyInUpdateProbabilities(undefinedConstantVariables); + } + + return true; + } + std::ostream& operator<<(std::ostream& stream, Module const& module) { stream << "module " << module.getName() << std::endl; for (auto const& booleanVariable : module.getBooleanVariables()) { diff --git a/src/storage/prism/Module.h b/src/storage/prism/Module.h index e8686cce5..0dc1f4dde 100644 --- a/src/storage/prism/Module.h +++ b/src/storage/prism/Module.h @@ -196,6 +196,14 @@ namespace storm { */ Module substitute(std::map const& substitution) const; + /*! + * Checks whether the given variables only appear in the update probabilities of the module and nowhere else. + * + * @param undefinedConstantVariables A set of variables that may only appear in the update probabilities. + * @return True iff the given variables only appear in the update probabilities of the module and nowhere else. + */ + bool containsVariablesOnlyInUpdateProbabilities(std::set const& undefinedConstantVariables) const; + friend std::ostream& operator<<(std::ostream& stream, Module const& module); private: diff --git a/src/storage/prism/Program.cpp b/src/storage/prism/Program.cpp index 5b2068a22..fc746ab00 100644 --- a/src/storage/prism/Program.cpp +++ b/src/storage/prism/Program.cpp @@ -11,7 +11,7 @@ namespace storm { namespace prism { - Program::Program(std::shared_ptr manager, ModelType modelType, std::vector const& constants, std::vector const& globalBooleanVariables, std::vector const& globalIntegerVariables, std::vector const& formulas, std::vector const& modules, std::map const& actionToIndexMap, std::vector const& rewardModels, bool fixInitialConstruct, storm::prism::InitialConstruct const& initialConstruct, std::vector