@ -11,6 +11,8 @@
# include "storm/exceptions/InvalidOperationException.h"
# include "storm/exceptions/InvalidTypeException.h"
# include "storm/solver/SmtSolver.h"
namespace storm {
namespace jani {
@ -53,6 +55,266 @@ namespace storm {
return name ;
}
void Model : : flattenSynchronizationVector ( Automaton & newAutomaton , std : : unordered_map < std : : vector < uint64_t > , uint64_t > & newLocations , std : : unordered_map < std : : string , uint64_t > & newActionToIndex , std : : vector < std : : set < uint64_t > > & synchronizingActionIndices , SynchronizationVector const & vector , std : : vector < std : : reference_wrapper < Automaton const > > const & composedAutomata , storm : : solver : : SmtSolver & solver ) {
// Gather all participating automata and the corresponding input symbols.
std : : vector < std : : pair < std : : reference_wrapper < Automaton const > , uint64_t > > participatingAutomataAndActions ;
for ( uint64_t i = 0 ; i < composedAutomata . size ( ) ; + + i ) {
std : : string const & actionName = vector . getInput ( i ) ;
if ( ! SynchronizationVector : : isNoActionInput ( actionName ) ) {
uint64_t actionIndex = getActionIndex ( actionName ) ;
participatingAutomataAndActions . push_back ( std : : make_pair ( composedAutomata [ i ] , actionIndex ) ) ;
synchronizingActionIndices [ i ] . insert ( actionIndex ) ;
}
}
}
Model Model : : flattenComposition ( std : : shared_ptr < storm : : utility : : solver : : SmtSolverFactory > const & smtSolverFactory ) const {
// If there is only one automaton and then system composition is the standard one, we don't need to modify
// the model.
if ( this - > getNumberOfAutomata ( ) = = 1 & & this - > hasStandardComposition ( ) ) {
return * this ;
}
// Check for current restrictions of flatting process.
STORM_LOG_THROW ( this - > hasStandardCompliantComposition ( ) , storm : : exceptions : : WrongFormatException , " Flatting composition is only supported for standard-compliant compositions. " ) ;
STORM_LOG_THROW ( this - > getModelType ( ) = = ModelType : : DTMC | | this - > getModelType ( ) = = ModelType : : MDP , storm : : exceptions : : InvalidTypeException , " Unable to flatten modules for model of type ' " < < this - > getModelType ( ) < < " '. " ) ;
// Otherwise, we need to actually flatten composition.
// Get an SMT solver for computing possible guard combinations.
std : : unique_ptr < storm : : solver : : SmtSolver > solver = smtSolverFactory - > create ( * expressionManager ) ;
Composition const & systemComposition = getSystemComposition ( ) ;
if ( systemComposition . isAutomatonComposition ( ) ) {
AutomatonComposition const & automatonComposition = systemComposition . asAutomatonComposition ( ) ;
STORM_LOG_THROW ( automatonComposition . getInputEnabledActions ( ) . empty ( ) , storm : : exceptions : : WrongFormatException , " Flatting does not support input-enabling actions. " ) ;
return createModelFromAutomaton ( getAutomaton ( automatonComposition . getAutomatonName ( ) ) ) ;
}
// Ensure that we have a parallel composition from now on.
STORM_LOG_THROW ( systemComposition . isParallelComposition ( ) , storm : : exceptions : : WrongFormatException , " Unknown system composition cannot be flattened. " ) ;
ParallelComposition const & parallelComposition = systemComposition . asParallelComposition ( ) ;
Automaton newAutomaton ( this - > getName ( ) + " _flattening " ) ;
for ( auto const & variable : getGlobalVariables ( ) ) {
std : : unique_ptr < Variable > renamedVariable = variable . clone ( ) ;
renamedVariable - > setName ( " global_ " + renamedVariable - > getName ( ) ) ;
newAutomaton . addVariable ( * renamedVariable ) ;
}
std : : vector < std : : reference_wrapper < Automaton const > > composedAutomata ;
for ( auto const & element : parallelComposition . getSubcompositions ( ) ) {
STORM_LOG_THROW ( element - > isAutomatonComposition ( ) , storm : : exceptions : : WrongFormatException , " Cannot flatten recursive (not standard-compliant) composition. " ) ;
AutomatonComposition const & automatonComposition = element - > asAutomatonComposition ( ) ;
STORM_LOG_THROW ( automatonComposition . getInputEnabledActions ( ) . empty ( ) , storm : : exceptions : : WrongFormatException , " Flatting does not support input-enabling actions. " ) ;
Automaton const & oldAutomaton = this - > getAutomaton ( automatonComposition . getAutomatonName ( ) ) ;
composedAutomata . push_back ( oldAutomaton ) ;
// Prefix all variables of this automaton with the automaton's name and add the to the resulting automaton.
for ( auto const & variable : oldAutomaton . getVariables ( ) ) {
std : : unique_ptr < Variable > renamedVariable = variable . clone ( ) ;
renamedVariable - > setName ( oldAutomaton . getName ( ) + " _ " + renamedVariable - > getName ( ) ) ;
newAutomaton . addVariable ( * renamedVariable ) ;
}
}
// Prepare the solver.
// Assert the values of the constants.
for ( auto const & constant : this - > getConstants ( ) ) {
if ( constant . isDefined ( ) ) {
solver - > add ( constant . getExpressionVariable ( ) = = constant . getExpression ( ) ) ;
}
}
// Assert the bounds of the global variables.
for ( auto const & variable : newAutomaton . getVariables ( ) . getBoundedIntegerVariables ( ) ) {
solver - > add ( variable . getExpressionVariable ( ) > = variable . getLowerBound ( ) ) ;
solver - > add ( variable . getExpressionVariable ( ) < = variable . getUpperBound ( ) ) ;
}
// Since we need to reduce a tuple of locations to a single location, we keep a hashmap for this.
std : : unordered_map < std : : vector < uint64_t > , uint64_t > newLocations ;
std : : unordered_map < std : : string , uint64_t > newActionToIndex ;
// Perform all necessary synchronizations and keep track which action indices participate in synchronization.
std : : vector < std : : set < uint64_t > > synchronizingActionIndices ;
for ( auto const & vector : parallelComposition . getSynchronizationVectors ( ) ) {
// If less then 2 automata participate, there is no need to perform a synchronization.
if ( vector . getNumberOfActionInputs ( ) < = 1 ) {
continue ;
}
flattenSynchronizationVector ( newAutomaton , newLocations , newActionToIndex , synchronizingActionIndices , vector , composedAutomata , * solver ) ;
}
// Now add all edges with action indices that were not mentioned in synchronization vectors.
for ( uint64_t i = 0 ; i < composedAutomata . size ( ) ; + + i ) {
Automaton const & automaton = composedAutomata [ i ] . get ( ) ;
for ( auto const & edge : automaton . getEdges ( ) ) {
if ( synchronizingActionIndices [ i ] . find ( edge . getActionIndex ( ) ) = = synchronizingActionIndices [ i ] . end ( ) ) {
// TODO: create template edge and create all concrete edges.
}
}
}
//
//
//
// // Now go through the modules, gather the variables, construct the name of the new module and assert the
// // bounds of the discovered variables.
// for (auto const& module : this->getModules()) {
// newModuleName << module.getName() << "_";
// allBooleanVariables.insert(allBooleanVariables.end(), module.getBooleanVariables().begin(), module.getBooleanVariables().end());
// allIntegerVariables.insert(allIntegerVariables.end(), module.getIntegerVariables().begin(), module.getIntegerVariables().end());
//
// // The commands without a synchronizing action name, can simply be copied (plus adjusting the global
// // indices of the command and its updates).
// for (auto const& command : module.getCommands()) {
// if (!command.isLabeled()) {
// std::vector<storm::prism::Update> updates;
// updates.reserve(command.getUpdates().size());
//
// for (auto const& update : command.getUpdates()) {
// updates.push_back(storm::prism::Update(nextUpdateIndex, update.getLikelihoodExpression(), update.getAssignments(), update.getFilename(), 0));
// ++nextUpdateIndex;
// }
//
// newCommands.push_back(storm::prism::Command(nextCommandIndex, command.isMarkovian(), actionToIndexMap.find("")->second, "", command.getGuardExpression(), updates, command.getFilename(), 0));
// ++nextCommandIndex;
// }
// }
// }
//
// // Save state of solver so that we can always restore the point where we have exactly the constant values
// // and variables bounds on the assertion stack.
// solver->push();
//
// // Now we need to enumerate all possible combinations of synchronizing commands. For this, we iterate over
// // all actions and let the solver enumerate the possible combinations of commands that can be enabled together.
// for (auto const& actionIndex : this->getSynchronizingActionIndices()) {
// bool noCombinationsForAction = false;
//
// // Prepare the list that stores for each module the list of commands with the given action.
// std::vector<std::vector<std::reference_wrapper<storm::prism::Command const>>> possibleCommands;
//
// for (auto const& module : this->getModules()) {
// // If the module has no command with this action, we can skip it.
// if (!module.hasActionIndex(actionIndex)) {
// continue;
// }
//
// std::set<uint_fast64_t> const& commandIndices = module.getCommandIndicesByActionIndex(actionIndex);
//
// // If there is no command even though the module has this action, there is no valid command
// // combination with this action.
// if (commandIndices.empty()) {
// noCombinationsForAction = true;
// break;
// }
//
// // Prepare empty list of commands for this module.
// possibleCommands.push_back(std::vector<std::reference_wrapper<storm::prism::Command const>>());
//
// // Add references to the commands labeled with the current action.
// for (auto const& commandIndex : commandIndices) {
// possibleCommands.back().push_back(module.getCommand(commandIndex));
// }
// }
//
// // If there are no valid combinations for the action, we need to skip the generation of synchronizing
// // commands.
// if (!noCombinationsForAction) {
// // Save the solver state to be able to restore it when this action index is done.
// solver->push();
//
// // Start by creating a fresh auxiliary variable for each command and link it with the guard.
// std::vector<std::vector<storm::expressions::Variable>> commandVariables(possibleCommands.size());
// std::vector<storm::expressions::Variable> allCommandVariables;
// for (uint_fast64_t outerIndex = 0; outerIndex < possibleCommands.size(); ++outerIndex) {
// // Create auxiliary variables and link them with the guards.
// for (uint_fast64_t innerIndex = 0; innerIndex < possibleCommands[outerIndex].size(); ++innerIndex) {
// commandVariables[outerIndex].push_back(manager->declareFreshBooleanVariable());
// allCommandVariables.push_back(commandVariables[outerIndex].back());
// solver->add(implies(commandVariables[outerIndex].back(), possibleCommands[outerIndex][innerIndex].get().getGuardExpression()));
// }
//
// storm::expressions::Expression atLeastOneCommandFromModule = manager->boolean(false);
// for (auto const& commandVariable : commandVariables[outerIndex]) {
// atLeastOneCommandFromModule = atLeastOneCommandFromModule || commandVariable;
// }
// solver->add(atLeastOneCommandFromModule);
// }
//
// // Now we are in a position to start the enumeration over all command variables. While doing so, we
// // keep track of previously seen command combinations, because the AllSat procedures are not
// // always guaranteed to only provide distinct models.
// std::unordered_set<std::vector<uint_fast64_t>, storm::utility::vector::VectorHash<uint_fast64_t>> seenCommandCombinations;
// solver->allSat(allCommandVariables, [&] (storm::solver::SmtSolver::ModelReference& modelReference) -> bool {
// // Now we need to reconstruct the chosen commands from the valuation of the command variables.
// std::vector<std::vector<std::reference_wrapper<Command const>>> chosenCommands(possibleCommands.size());
//
// for (uint_fast64_t outerIndex = 0; outerIndex < commandVariables.size(); ++outerIndex) {
// for (uint_fast64_t innerIndex = 0; innerIndex < commandVariables[outerIndex].size(); ++innerIndex) {
// if (modelReference.getBooleanValue(commandVariables[outerIndex][innerIndex])) {
// chosenCommands[outerIndex].push_back(possibleCommands[outerIndex][innerIndex]);
// }
// }
// }
//
// // Now that we have retrieved the commands, we need to build their synchronizations and add them
// // to the flattened module.
// std::vector<std::vector<std::reference_wrapper<Command const>>::const_iterator> iterators;
// for (auto const& element : chosenCommands) {
// iterators.push_back(element.begin());
// }
//
// bool movedAtLeastOneIterator = false;
// std::vector<std::reference_wrapper<Command const>> commandCombination(chosenCommands.size(), chosenCommands.front().front());
// std::vector<uint_fast64_t> commandCombinationIndices(iterators.size());
// do {
// for (uint_fast64_t index = 0; index < iterators.size(); ++index) {
// commandCombination[index] = *iterators[index];
// commandCombinationIndices[index] = commandCombination[index].get().getGlobalIndex();
// }
//
// // Only add the command combination if it was not previously seen.
// auto seenIt = seenCommandCombinations.find(commandCombinationIndices);
// if (seenIt == seenCommandCombinations.end()) {
// newCommands.push_back(synchronizeCommands(nextCommandIndex, actionIndex, nextUpdateIndex, indexToActionMap.find(actionIndex)->second, commandCombination));
// seenCommandCombinations.insert(commandCombinationIndices);
//
// // Move the counters appropriately.
// ++nextCommandIndex;
// nextUpdateIndex += newCommands.back().getNumberOfUpdates();
// }
//
// movedAtLeastOneIterator = false;
// for (uint_fast64_t index = 0; index < iterators.size(); ++index) {
// ++iterators[index];
// if (iterators[index] != chosenCommands[index].cend()) {
// movedAtLeastOneIterator = true;
// break;
// } else {
// iterators[index] = chosenCommands[index].cbegin();
// }
// }
// } while (movedAtLeastOneIterator);
//
// return true;
// });
//
// solver->pop();
// }
// }
//
// // Finally, we can create the module and the program and return it.
// storm::prism::Module singleModule(newModuleName.str(), allBooleanVariables, allIntegerVariables, newCommands, this->getFilename(), 0);
// return Program(manager, this->getModelType(), this->getConstants(), std::vector<storm::prism::BooleanVariable>(), std::vector<storm::prism::IntegerVariable>(), this->getFormulas(), {singleModule}, actionToIndexMap, this->getRewardModels(), this->getLabels(), this->getOptionalInitialConstruct(), this->getOptionalSystemCompositionConstruct(), this->getFilename(), 0, true);
}
uint64_t Model : : addAction ( Action const & action ) {
auto it = actionToIndex . find ( action . getName ( ) ) ;
STORM_LOG_THROW ( it = = actionToIndex . end ( ) , storm : : exceptions : : WrongFormatException , " Action with name ' " < < action . getName ( ) < < " ' already exists " ) ;
@ -609,5 +871,18 @@ namespace storm {
}
return false ;
}
Model Model : : createModelFromAutomaton ( Automaton const & automaton ) const {
// Copy the full model
Model newModel ( * this ) ;
// Replace the automata by the one single selected automaton.
newModel . automata = std : : vector < Automaton > ( { automaton } ) ;
// Set the standard composition for the new model to the default one.
newModel . setSystemComposition ( newModel . getStandardSystemComposition ( ) ) ;
return newModel ;
}
}
}