#include "src/builder/jit/ExplicitJitJaniModelBuilder.h" #include <iostream> #include <cstdio> #include <chrono> #include "src/adapters/CarlAdapter.h" #include "src/solver/SmtSolver.h" #include "src/storage/jani/AutomatonComposition.h" #include "src/storage/jani/ParallelComposition.h" #include "src/models/sparse/Dtmc.h" #include "src/models/sparse/StandardRewardModel.h" #include "src/utility/macros.h" #include "src/utility/solver.h" #include "src/exceptions/WrongFormatException.h" #include "src/exceptions/InvalidStateException.h" #include "src/exceptions/InvalidArgumentException.h" #include "src/exceptions/NotSupportedException.h" #include "src/exceptions/UnexpectedException.h" namespace storm { namespace builder { namespace jit { static const std::string CXX_COMPILER = "clang++"; static const std::string DYLIB_EXTENSION = ".dylib"; static const std::string COMPILER_FLAGS = "-std=c++11 -stdlib=libc++ -fPIC -O3 -shared -funroll-loops -undefined dynamic_lookup"; static const std::string STORM_ROOT = "/Users/chris/work/storm"; static const std::string L3PP_ROOT = "/Users/chris/work/storm/resources/3rdparty/l3pp"; static const std::string BOOST_ROOT = "/usr/local/Cellar/boost/1.62.0/include"; static const std::string GMP_ROOT = "/usr/local/Cellar/gmp/6.1.1"; static const std::string CARL_ROOT = "/Users/chris/work/carl"; static const std::string CLN_ROOT = "/usr/local/Cellar/cln/1.3.4"; static const std::string GINAC_ROOT = "/usr/local/Cellar/ginac/1.6.7_1"; template <typename ValueType, typename RewardModelType> ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::ExplicitJitJaniModelBuilder(storm::jani::Model const& model, storm::builder::BuilderOptions const& options) : options(options), model(model), modelComponentsBuilder(model.getModelType()) { for (auto const& variable : this->model.getGlobalVariables().getTransientVariables()) { transientVariables.insert(variable.getExpressionVariable()); } for (auto& automaton : this->model.getAutomata()) { // FIXME: just for testing purposes. automaton.pushEdgeAssignmentsToDestinations(); automatonToLocationVariable.emplace(automaton.getName(), model.getManager().declareFreshIntegerVariable(false, automaton.getName() + "_")); } // 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 (!std::is_same<ValueType, storm::RationalFunction>::value && model.hasUndefinedConstants()) { #else if (model.hasUndefinedConstants()) { #endif std::vector<std::reference_wrapper<storm::jani::Constant const>> undefinedConstants = model.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, "Model still contains these undefined constants: " + stream.str()); } #ifdef STORM_HAVE_CARL else if (std::is_same<ValueType, storm::RationalFunction>::value && !model.undefinedConstantsAreGraphPreserving()) { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The input model contains undefined constants that influence the graph structure of the underlying model, which is not allowed."); } #endif } template <typename ValueType, typename RewardModelType> std::string ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::createSourceCode() { std::string sourceTemplate = R"( #define NDEBUG #include <cstdint> #include <iostream> #include <vector> #include <queue> #include <cmath> #include <unordered_map> #include <boost/dll/alias.hpp> #include "resources/3rdparty/sparsepp/sparsepp.h" #include "src/builder/jit/StateSet.h" #include "src/builder/jit/JitModelBuilderInterface.h" #include "src/builder/jit/StateBehaviour.h" #include "src/builder/jit/ModelComponentsBuilder.h" namespace storm { namespace builder { namespace jit { typedef uint32_t IndexType; typedef double ValueType; struct StateType { // Boolean variables. {% for variable in nontransient_variables.boolean %}bool {$variable.name} : 1; {% endfor %} // Bounded integer variables. {% for variable in nontransient_variables.boundedInteger %}uint64_t {$variable.name} : {$variable.numberOfBits}; {% endfor %} // Unbounded integer variables. {% for variable in nontransient_variables.unboundedInteger %}int64_t {$variable.name}; {% endfor %} // Real variables. {% for variable in nontransient_variables.real %}double {$variable.name}; {% endfor %} // Location variables. {% for variable in nontransient_variables.locations %}uint64_t {$variable.name} : {$variable.numberOfBits}; {% endfor %} }; bool operator==(StateType const& first, StateType const& second) { bool result = true; {% for variable in nontransient_variables.boolean %}result &= !(first.{$variable.name} ^ second.{$variable.name}); {% endfor %} {% for variable in nontransient_variables.boundedInteger %}result &= first.{$variable.name} == second.{$variable.name}; {% endfor %} {% for variable in nontransient_variables.unboundedInteger %}result &= first.{$variable.name} == second.{$variable.name}; {% endfor %} {% for variable in nontransient_variables.real %}result &= first.{$variable.name} == second.{$variable.name}; {% endfor %} {% for variable in nontransient_variables.locations %}result &= first.{$variable.name} == second.{$variable.name}; {% endfor %} return result; } std::ostream& operator<<(std::ostream& out, StateType const& in) { out << "<"; {% for variable in nontransient_variables.boolean %}out << "{$variable.name}=" << std::boolalpha << in.{$variable.name} << ", " << std::noboolalpha; {% endfor %} {% for variable in nontransient_variables.boundedInteger %}out << "{$variable.name}=" << in.{$variable.name} << ", "; {% endfor %} {% for variable in nontransient_variables.unboundedInteger %}out << "{$variable.name}=" << in.{$variable.name} << ", "; {% endfor %} {% for variable in nontransient_variables.real %}out << "{$variable.name}=" << in.{$variable.name} << ", "; {% endfor %} {% for variable in nontransient_variables.locations %}out << "{$variable.name}=" << in.{$variable.name} << ", "; {% endfor %} out << ">"; return out; } struct TransientVariables { TransientVariables() { reset(); } void reset() { {% for variable in transient_variables.boolean %}{$variable.name} = {$variable.init}; {% endfor %} {% for variable in transient_variables.boundedInteger %}{$variable.name} = {$variable.init}; {% endfor %} {% for variable in transient_variables.unboundedInteger %}{$variable.name} = {$variable.init}; {% endfor %} {% for variable in transient_variables.real %}{$variable.name} = {$variable.init}; {% endfor %} } // Boolean variables. {% for variable in transient_variables.boolean %}bool {$variable.name} : 1; {% endfor %} // Bounded integer variables. {% for variable in transient_variables.boundedInteger %}uint64_t {$variable.name} : {$variable.numberOfBits}; {% endfor %} // Unbounded integer variables. {% for variable in transient_variables.unboundedInteger %}int64_t {$variable.name}; {% endfor %} // Real variables. {% for variable in transient_variables.real %}double {$variable.name}; {% endfor %} }; std::ostream& operator<<(std::ostream& out, TransientVariables const& in) { out << "<"; {% for variable in transient_variables.boolean %}out << "{$variable.name}=" << std::boolalpha << in.{$variable.name} << ", " << std::noboolalpha; {% endfor %} {% for variable in transient_variables.boundedInteger %}out << "{$variable.name}=" << in.{$variable.name} << ", "; {% endfor %} {% for variable in transient_variables.unboundedInteger %}out << "{$variable.name}=" << in.{$variable.name} << ", "; {% endfor %} {% for variable in transient_variables.real %}out << "{$variable.name}=" << in.{$variable.name} << ", "; {% endfor %} out << ">"; return out; } } } } namespace std { template <> struct hash<storm::builder::jit::StateType> { std::size_t operator()(storm::builder::jit::StateType const& in) const { // Note: this is faster than viewing the struct as a bit field and taking hash_combine of the bytes. std::size_t seed = 0; {% for variable in nontransient_variables.boolean %}spp::hash_combine(seed, in.{$variable.name}); {% endfor %} {% for variable in nontransient_variables.boundedInteger %}spp::hash_combine(seed, in.{$variable.name}); {% endfor %} {% for variable in nontransient_variables.unboundedInteger %}spp::hash_combine(seed, in.{$variable.name}); {% endfor %} {% for variable in nontransient_variables.real %}spp::hash_combine(seed, in.{$variable.name}); {% endfor %} {% for variable in nontransient_variables.locations %}spp::hash_combine(seed, in.{$variable.name}); {% endfor %} return seed; } }; } namespace storm { namespace builder { namespace jit { static bool model_is_deterministic() { return {$deterministic_model}; } static bool model_is_discrete_time() { return {$discrete_time_model}; } // Non-synchronizing edges. {% for edge in nonsynch_edges %}static bool edge_enabled_{$edge.name}(StateType const& in) { if ({$edge.guard}) { return true; } return false; } static void edge_perform_{$edge.name}(StateType const& in, TransientVariables const& transientIn, TransientVariables& transientOut) { {% for assignment in edge.transient_assignments %}transientOut.{$assignment.variable} = {$assignment.value}; {% endfor %} } {% for destination in edge.destinations %} static void destination_perform_level_{$edge.name}_{$destination.name}(int_fast64_t level, StateType const& in, StateType& out) { {% for level in destination.levels %}if (level == {$level.index}) { {% for assignment in level.non_transient_assignments %}out.{$assignment.variable} = {$assignment.value}; {% endfor %} } {% endfor %} } static void destination_perform_{$edge.name}_{$destination.name}(StateType const& in, StateType& out) { {% for level in destination.levels %} destination_perform_level_{$edge.name}_{$destination.name}({$level.index}, in, out); {% endfor %} } static void destination_perform_level_{$edge.name}_{$destination.name}(int_fast64_t level, StateType const& in, StateType& out, TransientVariables const& transientIn, TransientVariables& transientOut) { {% for level in destination.levels %}if (level == {$level.index}) { {% for assignment in level.non_transient_assignments %}out.{$assignment.variable} = {$assignment.value}; {% endfor %} {% for assignment in level.transient_assignments %}transientOut.{$assignment.variable} = {$assignment.value}; {% endfor %} } {% endfor %} } static void destination_perform_{$edge.name}_{$destination.name}(StateType const& in, StateType& out, TransientVariables const& transientIn, TransientVariables& transientOut) { {% for level in destination.levels %} destination_perform_level_{$edge.name}_{$destination.name}({$level.index}, in, out, transientIn, transientOut); {% endfor %} } {% endfor %}{% endfor %} // Synchronizing edges. {% for edge in synch_edges %}static bool edge_enabled_{$edge.name}(StateType const& in) { if ({$edge.guard}) { return true; } return false; } static void edge_perform_{$edge.name}(StateType const& in, TransientVariables const& transientIn, TransientVariables& transientOut) { {% for assignment in edge.transient_assignments %}transientOut.{$assignment.variable} = {$assignment.value}; {% endfor %} } {% for destination in edge.destinations %} static void destination_perform_level_{$edge.name}_{$destination.name}(int_fast64_t level, StateType const& in, StateType& out) { {% for level in destination.levels %}if (level == {$level.index}) { {% for assignment in level.non_transient_assignments %}out.{$assignment.variable} = {$assignment.value}; {% endfor %} } {% endfor %} } static void destination_perform_{$edge.name}_{$destination.name}(StateType const& in, StateType& out) { {% for level in destination.levels %} destination_perform_level_{$edge.name}_{$destination.name}({$level.index}, in, out); {% endfor %} } static void destination_perform_level_{$edge.name}_{$destination.name}(int_fast64_t level, StateType const& in, StateType& out, TransientVariables const& transientIn, TransientVariables& transientOut) { {% for level in destination.levels %}if (level == {$level.index}) { {% for assignment in level.non_transient_assignments %}out.{$assignment.variable} = {$assignment.value}; {% endfor %} {% for assignment in level.transient_assignments %}transientOut.{$assignment.variable} = {$assignment.value}; {% endfor %} } {% endfor %} } static void destination_perform_{$edge.name}_{$destination.name}(StateType const& in, StateType& out, TransientVariables const& transientIn, TransientVariables& transientOut) { {% for level in destination.levels %} destination_perform_level_{$edge.name}_{$destination.name}({$level.index}, in, out, transientIn, transientOut); {% endfor %} } {% endfor %}{% endfor %} typedef void (*DestinationLevelFunctionPtr)(int_fast64_t, StateType const&, StateType&, TransientVariables const&, TransientVariables&); typedef void (*DestinationFunctionPtr)(StateType const&, StateType&, TransientVariables const&, TransientVariables&); typedef void (*DestinationWithoutTransientLevelFunctionPtr)(int_fast64_t, StateType const&, StateType&); typedef void (*DestinationWithoutTransientFunctionPtr)(StateType const&, StateType&); class Destination { public: Destination() : mLowestLevel(0), mHighestLevel(0), mValue(), destinationLevelFunction(nullptr), destinationFunction(nullptr), destinationWithoutTransientLevelFunction(nullptr), destinationWithoutTransientFunction(nullptr) { // Intentionally left empty. } Destination(int_fast64_t lowestLevel, int_fast64_t highestLevel, ValueType const& value, DestinationLevelFunctionPtr destinationLevelFunction, DestinationFunctionPtr destinationFunction, DestinationWithoutTransientLevelFunctionPtr destinationWithoutTransientLevelFunction, DestinationWithoutTransientFunctionPtr destinationWithoutTransientFunction) : mLowestLevel(lowestLevel), mHighestLevel(highestLevel), mValue(value), destinationLevelFunction(destinationLevelFunction), destinationFunction(destinationFunction), destinationWithoutTransientLevelFunction(destinationWithoutTransientLevelFunction), destinationWithoutTransientFunction(destinationWithoutTransientFunction) { // Intentionally left empty. } int_fast64_t lowestLevel() const { return mLowestLevel; } int_fast64_t highestLevel() const { return mHighestLevel; } ValueType const& value() const { return mValue; } void perform(int_fast64_t level, StateType const& in, StateType& out, TransientVariables const& transientIn, TransientVariables& transientOut) const { destinationLevelFunction(level, in, out, transientIn, transientOut); } void perform(StateType const& in, StateType& out, TransientVariables const& transientIn, TransientVariables& transientOut) const { destinationFunction(in, out, transientIn, transientOut); } void perform(int_fast64_t level, StateType const& in, StateType& out) const { destinationWithoutTransientLevelFunction(level, in, out); } void perform(StateType const& in, StateType& out) const { destinationWithoutTransientFunction(in, out); } private: int_fast64_t mLowestLevel; int_fast64_t mHighestLevel; ValueType mValue; DestinationLevelFunctionPtr destinationLevelFunction; DestinationFunctionPtr destinationFunction; DestinationWithoutTransientLevelFunctionPtr destinationWithoutTransientLevelFunction; DestinationWithoutTransientFunctionPtr destinationWithoutTransientFunction; }; typedef bool (*EdgeEnabledFunctionPtr)(StateType const&); typedef void (*EdgeTransientFunctionPtr)(StateType const&, TransientVariables const& transientIn, TransientVariables& out); class Edge { public: typedef std::vector<Destination> ContainerType; Edge() : edgeEnabledFunction(nullptr) { // Intentionally left empty. } Edge(EdgeEnabledFunctionPtr edgeEnabledFunction, EdgeTransientFunctionPtr edgeTransientFunction = nullptr) : edgeEnabledFunction(edgeEnabledFunction), edgeTransientFunction(edgeTransientFunction) { // Intentionally left empty. } bool isEnabled(StateType const& in) const { return edgeEnabledFunction(in); } void addDestination(Destination const& destination) { destinations.push_back(destination); } void addDestination(int_fast64_t lowestLevel, int_fast64_t highestLevel, ValueType const& value, DestinationLevelFunctionPtr destinationLevelFunction, DestinationFunctionPtr destinationFunction, DestinationWithoutTransientLevelFunctionPtr destinationWithoutTransientLevelFunction, DestinationWithoutTransientFunctionPtr destinationWithoutTransientFunction) { destinations.emplace_back(lowestLevel, highestLevel, value, destinationLevelFunction, destinationFunction, destinationWithoutTransientLevelFunction, destinationWithoutTransientFunction); } std::vector<Destination> const& getDestinations() const { return destinations; } ContainerType::const_iterator begin() const { return destinations.begin(); } ContainerType::const_iterator end() const { return destinations.end(); } void perform(StateType const& in, TransientVariables const& transientIn, TransientVariables& transientOut) const { edgeTransientFunction(in, transientIn, transientOut); } private: EdgeEnabledFunctionPtr edgeEnabledFunction; EdgeTransientFunctionPtr edgeTransientFunction; ContainerType destinations; }; void locations_perform(StateType const& in, TransientVariables const& transientIn, TransientVariables& out) { {% for location in locations %}if ({$location.guard}) { {% for assignment in location.assignments %}transientOut.{$assignment.variable} = {$assignment.value};{% endfor %} } {% endfor %} } class JitBuilder : public JitModelBuilderInterface<IndexType, ValueType> { public: JitBuilder(ModelComponentsBuilder<IndexType, ValueType>& modelComponentsBuilder) : JitModelBuilderInterface(modelComponentsBuilder) { {% for state in initialStates %}{ StateType state; {% for assignment in state %}state.{$assignment.variable} = {$assignment.value}; {% endfor %} initialStates.push_back(state); }{% endfor %} {% for edge in nonsynch_edges %}{ edge_{$edge.name} = Edge(&edge_enabled_{$edge.name}{% if edge.transient_assignments %}, edge_perform_{$edge.name}{% endif %}); {% for destination in edge.destinations %}edge_{$edge.name}.addDestination({$destination.lowestLevel}, {$destination.highestLevel}, {$destination.value}, &destination_perform_level_{$edge.name}_{$destination.name}, &destination_perform_{$edge.name}_{$destination.name}, &destination_perform_level_{$edge.name}_{$destination.name}, &destination_perform_{$edge.name}_{$destination.name}); {% endfor %} } {% endfor %} {% for edge in synch_edges %}{ edge_{$edge.name} = Edge(&edge_enabled_{$edge.name}{% if edge.transient_assignments %}, edge_perform_{$edge.name}{% endif %}); {% for destination in edge.destinations %}edge_{$edge.name}.addDestination({$destination.lowestLevel}, {$destination.highestLevel}, {$destination.value}, &destination_perform_level_{$edge.name}_{$destination.name}, &destination_perform_{$edge.name}_{$destination.name}, &destination_perform_level_{$edge.name}_{$destination.name}, &destination_perform_{$edge.name}_{$destination.name}); {% endfor %} } {% endfor %} } virtual storm::models::sparse::Model<ValueType, storm::models::sparse::StandardRewardModel<ValueType>>* build() override { std::cout << "starting building process" << std::endl; explore(initialStates); std::cout << "finished building process with " << stateIds.size() << " states" << std::endl; std::cout << "building labeling" << std::endl; label(); std::cout << "finished building labeling" << std::endl; return this->modelComponentsBuilder.build(stateIds.size()); } void label() { uint64_t labelCount = 0; {% for label in labels %}this->modelComponentsBuilder.registerLabel("{$label.name}", stateIds.size()); ++labelCount; {% endfor %} this->modelComponentsBuilder.registerLabel("init", stateIds.size()); this->modelComponentsBuilder.registerLabel("deadlock", stateIds.size()); for (auto const& stateEntry : stateIds) { auto const& state = stateEntry.first; {% for label in labels %}if ({$label.predicate}) { this->modelComponentsBuilder.addLabel(stateEntry.second, {$loop.index} - 1); } {% endfor %} } for (auto const& state : initialStates) { auto stateIt = stateIds.find(state); if (stateIt != stateIds.end()) { this->modelComponentsBuilder.addLabel(stateIt->second, labelCount); } } for (auto const& stateId : deadlockStates) { this->modelComponentsBuilder.addLabel(stateId, labelCount + 1); } } void explore(std::vector<StateType> const& initialStates) { for (auto const& state : initialStates) { explore(state); } } void explore(StateType const& initialState) { StateSet<StateType> statesToExplore; getOrAddIndex(initialState, statesToExplore); StateBehaviour<IndexType, ValueType> behaviour; while (!statesToExplore.empty()) { StateType currentState = statesToExplore.get(); IndexType currentIndex = getIndex(currentState); if (!isTerminalState(currentState)) { #ifndef NDEBUG std::cout << "Exploring state " << currentState << ", id " << currentIndex << std::endl; #endif behaviour.setExpanded(); // Perform transient location assignments. TransientVariables transientIn; TransientVariables transientOut; locations_perform(currentState, transientIn, transientOut); // Explore all edges that do not take part in synchronization vectors. exploreNonSynchronizingEdges(currentState, behaviour, statesToExplore); // Explore all edges that participate in synchronization vectors. exploreSynchronizingEdges(currentState, behaviour, statesToExplore); } this->addStateBehaviour(currentIndex, behaviour); behaviour.clear(); } } bool isTerminalState(StateType const& state) const { {% for expression in terminalExpressions %}if ({$expression}) { return true; } {% endfor %} return false; } void exploreNonSynchronizingEdges(StateType const& in, StateBehaviour<IndexType, ValueType>& behaviour, StateSet<StateType>& statesToExplore) { {% for edge in nonsynch_edges %}{ if ({$edge.guard}) { TransientVariables transientIn; TransientVariables transientOut; {% if edge.transient_assignments %} edge_perform_{$edge.name}(in, transientIn, transientOut); {% endif %} Choice<IndexType, ValueType>& choice = behaviour.addChoice(); {% for destination in edge.destinations %}{ StateType out(in); destination_perform_{$edge.name}_{$destination.name}(in, out{% if edge.transient_variables_in_destinations %}, transientIn, transientOut{% endif %}); IndexType outStateIndex = getOrAddIndex(out, statesToExplore); choice.add(outStateIndex, {$destination.value}); } {% endfor %} } } {% endfor %} } {% for vector in synch_vectors %}{$vector.functions} {% endfor %} void exploreSynchronizingEdges(StateType const& state, StateBehaviour<IndexType, ValueType>& behaviour, StateSet<StateType>& statesToExplore) { {% for vector in synch_vectors %}{ exploreSynchronizationVector_{$vector.index}(state, behaviour, statesToExplore); } {% endfor %} } IndexType getOrAddIndex(StateType const& state, StateSet<StateType>& statesToExplore) { auto it = stateIds.find(state); if (it != stateIds.end()) { return it->second; } else { IndexType newIndex = static_cast<IndexType>(stateIds.size()); stateIds.insert(std::make_pair(state, newIndex)); #ifndef NDEBUG std::cout << "inserting state " << state << std::endl; #endif statesToExplore.add(state); return newIndex; } } IndexType getIndex(StateType const& state) const { auto it = stateIds.find(state); if (it != stateIds.end()) { return it->second; } else { return stateIds.at(state); } } void addStateBehaviour(IndexType const& stateId, StateBehaviour<IndexType, ValueType>& behaviour) { if (behaviour.empty()) { deadlockStates.push_back(stateId); } JitModelBuilderInterface<IndexType, ValueType>::addStateBehaviour(stateId, behaviour); } static JitModelBuilderInterface<IndexType, ValueType>* create(ModelComponentsBuilder<IndexType, ValueType>& modelComponentsBuilder) { return new JitBuilder(modelComponentsBuilder); } private: spp::sparse_hash_map<StateType, IndexType> stateIds; std::vector<StateType> initialStates; std::vector<IndexType> deadlockStates; {% for edge in nonsynch_edges %}Edge edge_{$edge.name}; {% endfor %} {% for edge in synch_edges %}Edge edge_{$edge.name}; {% endfor %} }; BOOST_DLL_ALIAS(storm::builder::jit::JitBuilder::create, create_builder) } } } )"; cpptempl::data_map modelData; generateVariables(modelData); cpptempl::data_list initialStates = generateInitialStates(); modelData["initialStates"] = cpptempl::make_data(initialStates); generateEdges(modelData); generateLocations(modelData); generateRewards(modelData); cpptempl::data_list labels = generateLabels(); modelData["labels"] = cpptempl::make_data(labels); cpptempl::data_list terminalExpressions = generateTerminalExpressions(); modelData["terminalExpressions"] = cpptempl::make_data(terminalExpressions); modelData["deterministic_model"] = model.isDeterministicModel() ? "true" : "false"; modelData["discrete_time_model"] = model.isDiscreteTimeModel() ? "true" : "false"; return cpptempl::parse(sourceTemplate, modelData); } template <typename ValueType, typename RewardModelType> cpptempl::data_list ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateInitialStates() { cpptempl::data_list initialStates; // Prepare an SMT solver to enumerate all initial states. storm::utility::solver::SmtSolverFactory factory; std::unique_ptr<storm::solver::SmtSolver> solver = factory.create(model.getExpressionManager()); std::vector<storm::expressions::Expression> rangeExpressions = model.getAllRangeExpressions(); for (auto const& expression : rangeExpressions) { solver->add(expression); } solver->add(model.getInitialStatesExpression(true)); // Proceed as long as the solver can still enumerate initial states. while (solver->check() == storm::solver::SmtSolver::CheckResult::Sat) { // Create fresh state. cpptempl::data_list initialStateAssignment; // Read variable assignment from the solution of the solver. Also, create an expression we can use to // prevent the variable assignment from being enumerated again. storm::expressions::Expression blockingExpression; std::shared_ptr<storm::solver::SmtSolver::ModelReference> model = solver->getModel(); for (auto const& variable : this->model.getGlobalVariables().getBooleanVariables()) { storm::expressions::Variable const& expressionVariable = variable.getExpressionVariable(); bool variableValue = model->getBooleanValue(expressionVariable); initialStateAssignment.push_back(generateAssignment(variable, variableValue)); storm::expressions::Expression localBlockingExpression = variableValue ? !expressionVariable : expressionVariable; blockingExpression = blockingExpression.isInitialized() ? blockingExpression || localBlockingExpression : localBlockingExpression; } for (auto const& variable : this->model.getGlobalVariables().getBoundedIntegerVariables()) { storm::expressions::Variable const& expressionVariable = variable.getExpressionVariable(); int_fast64_t variableValue = model->getIntegerValue(expressionVariable); initialStateAssignment.push_back(generateAssignment(variable, variableValue)); storm::expressions::Expression localBlockingExpression = expressionVariable != model->getManager().integer(variableValue); blockingExpression = blockingExpression.isInitialized() ? blockingExpression || localBlockingExpression : localBlockingExpression; } for (auto const& automaton : this->model.getAutomata()) { for (auto const& variable : automaton.getVariables().getBooleanVariables()) { storm::expressions::Variable const& expressionVariable = variable.getExpressionVariable(); bool variableValue = model->getBooleanValue(expressionVariable); initialStateAssignment.push_back(generateAssignment(variable, variableValue)); storm::expressions::Expression localBlockingExpression = variableValue ? !expressionVariable : expressionVariable; blockingExpression = blockingExpression.isInitialized() ? blockingExpression || localBlockingExpression : localBlockingExpression; } for (auto const& variable : automaton.getVariables().getBoundedIntegerVariables()) { storm::expressions::Variable const& expressionVariable = variable.getExpressionVariable(); int_fast64_t variableValue = model->getIntegerValue(expressionVariable); initialStateAssignment.push_back(generateAssignment(variable, variableValue)); storm::expressions::Expression localBlockingExpression = expressionVariable != model->getManager().integer(variableValue); blockingExpression = blockingExpression.isInitialized() ? blockingExpression || localBlockingExpression : localBlockingExpression; } } // Gather iterators to the initial locations of all the automata. std::vector<std::set<uint64_t>::const_iterator> initialLocationsIterators; for (auto const& automaton : this->model.getAutomata()) { initialLocationsIterators.push_back(automaton.getInitialLocationIndices().cbegin()); } // Now iterate through all combinations of initial locations. while (true) { cpptempl::data_list completeAssignment(initialStateAssignment); for (uint64_t index = 0; index < initialLocationsIterators.size(); ++index) { storm::jani::Automaton const& automaton = this->model.getAutomata()[index]; if (automaton.getNumberOfLocations() > 1) { completeAssignment.push_back(generateLocationAssignment(automaton, *initialLocationsIterators[index])); } } initialStates.push_back(cpptempl::make_data(completeAssignment)); uint64_t index = 0; for (; index < initialLocationsIterators.size(); ++index) { ++initialLocationsIterators[index]; if (initialLocationsIterators[index] == this->model.getAutomata()[index].getInitialLocationIndices().cend()) { initialLocationsIterators[index] = this->model.getAutomata()[index].getInitialLocationIndices().cbegin(); } else { break; } } // If we are at the end, leave the loop. if (index == initialLocationsIterators.size()) { break; } } // Block the current initial state to search for the next one. if (!blockingExpression.isInitialized()) { break; } solver->add(blockingExpression); } return initialStates; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateBooleanVariable(storm::jani::BooleanVariable const& variable) { cpptempl::data_map result; result["name"] = registerVariable(variable.getExpressionVariable()); if (variable.hasInitExpression()) { result["init"] = asString(variable.getInitExpression().evaluateAsBool()); } return result; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateBoundedIntegerVariable(storm::jani::BoundedIntegerVariable const& variable) { cpptempl::data_map result; int_fast64_t lowerBound = variable.getLowerBound().evaluateAsInt(); int_fast64_t upperBound = variable.getUpperBound().evaluateAsInt(); lowerBounds[variable.getExpressionVariable()] = lowerBound; if (lowerBound != 0) { lowerBoundShiftSubstitution[variable.getExpressionVariable()] = variable.getExpressionVariable() + model.getManager().integer(lowerBound); } uint64_t range = static_cast<uint64_t>(upperBound - lowerBound + 1); uint64_t numberOfBits = static_cast<uint64_t>(std::ceil(std::log2(range))); result["name"] = registerVariable(variable.getExpressionVariable()); result["numberOfBits"] = std::to_string(numberOfBits); if (variable.hasInitExpression()) { result["init"] = asString(variable.getInitExpression().evaluateAsInt() - lowerBound); } return result; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateUnboundedIntegerVariable(storm::jani::UnboundedIntegerVariable const& variable) { cpptempl::data_map result; result["name"] = registerVariable(variable.getExpressionVariable()); if (variable.hasInitExpression()) { result["init"] = asString(variable.getInitExpression().evaluateAsInt()); } return result; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateRealVariable(storm::jani::RealVariable const& variable) { cpptempl::data_map result; result["name"] = registerVariable(variable.getExpressionVariable()); if (variable.hasInitExpression()) { result["init"] = asString(variable.getInitExpression().evaluateAsDouble()); } return result; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateLocationVariable(storm::jani::Automaton const& automaton) { cpptempl::data_map result; result["name"] = registerVariable(getLocationVariable(automaton)); result["numberOfBits"] = static_cast<uint64_t>(std::ceil(std::log2(automaton.getNumberOfLocations()))); return result; } template <typename ValueType, typename RewardModelType> void ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateVariables(cpptempl::data_map& modelData) { cpptempl::data_list nonTransientBooleanVariables; cpptempl::data_list transientBooleanVariables; cpptempl::data_list nonTransientBoundedIntegerVariables; cpptempl::data_list transientBoundedIntegerVariables; cpptempl::data_list nonTransientUnboundedIntegerVariables; cpptempl::data_list transientUnboundedIntegerVariables; cpptempl::data_list nonTransientRealVariables; cpptempl::data_list transientRealVariables; cpptempl::data_list locationVariables; for (auto const& variable : model.getGlobalVariables().getBooleanVariables()) { cpptempl::data_map newBooleanVariable = generateBooleanVariable(variable.asBooleanVariable()); if (variable.isTransient()) { transientBooleanVariables.push_back(newBooleanVariable); } else { nonTransientBooleanVariables.push_back(newBooleanVariable); } } for (auto const& variable : model.getGlobalVariables().getBoundedIntegerVariables()) { cpptempl::data_map newBoundedIntegerVariable = generateBoundedIntegerVariable(variable.asBoundedIntegerVariable()); if (variable.isTransient()) { transientBoundedIntegerVariables.push_back(newBoundedIntegerVariable); } else { nonTransientBoundedIntegerVariables.push_back(newBoundedIntegerVariable); } } for (auto const& variable : model.getGlobalVariables().getUnboundedIntegerVariables()) { cpptempl::data_map newUnboundedIntegerVariable = generateUnboundedIntegerVariable(variable.asUnboundedIntegerVariable()); if (variable.isTransient()) { transientUnboundedIntegerVariables.push_back(newUnboundedIntegerVariable); } else { nonTransientUnboundedIntegerVariables.push_back(newUnboundedIntegerVariable); } } for (auto const& variable : model.getGlobalVariables().getRealVariables()) { cpptempl::data_map newRealVariable = generateRealVariable(variable.asRealVariable()); if (variable.isTransient()) { transientRealVariables.push_back(newRealVariable); } else { nonTransientRealVariables.push_back(newRealVariable); } } for (auto const& automaton : model.getAutomata()) { for (auto const& variable : automaton.getVariables().getBooleanVariables()) { cpptempl::data_map newBooleanVariable = generateBooleanVariable(variable.asBooleanVariable()); if (variable.isTransient()) { transientBooleanVariables.push_back(newBooleanVariable); } else { nonTransientBooleanVariables.push_back(newBooleanVariable); } } for (auto const& variable : automaton.getVariables().getBoundedIntegerVariables()) { cpptempl::data_map newBoundedIntegerVariable = generateBoundedIntegerVariable(variable.asBoundedIntegerVariable()); if (variable.isTransient()) { transientBoundedIntegerVariables.push_back(newBoundedIntegerVariable); } else { nonTransientBoundedIntegerVariables.push_back(newBoundedIntegerVariable); } } for (auto const& variable : automaton.getVariables().getUnboundedIntegerVariables()) { cpptempl::data_map newUnboundedIntegerVariable = generateUnboundedIntegerVariable(variable.asUnboundedIntegerVariable()); if (variable.isTransient()) { transientUnboundedIntegerVariables.push_back(newUnboundedIntegerVariable); } else { nonTransientUnboundedIntegerVariables.push_back(newUnboundedIntegerVariable); } } for (auto const& variable : automaton.getVariables().getRealVariables()) { cpptempl::data_map newRealVariable = generateRealVariable(variable.asRealVariable()); if (variable.isTransient()) { transientRealVariables.push_back(newRealVariable); } else { nonTransientRealVariables.push_back(newRealVariable); } } // Only generate a location variable if there is more than one location for the automaton. if (automaton.getNumberOfLocations() > 1) { locationVariables.push_back(generateLocationVariable(automaton)); } } cpptempl::data_map nonTransientVariables; nonTransientVariables["boolean"] = cpptempl::make_data(nonTransientBooleanVariables); nonTransientVariables["boundedInteger"] = cpptempl::make_data(nonTransientBoundedIntegerVariables); nonTransientVariables["unboundedInteger"] = cpptempl::make_data(nonTransientUnboundedIntegerVariables); nonTransientVariables["real"] = cpptempl::make_data(nonTransientRealVariables); nonTransientVariables["locations"] = cpptempl::make_data(locationVariables); modelData["nontransient_variables"] = nonTransientVariables; cpptempl::data_map transientVariables; transientVariables["boolean"] = cpptempl::make_data(transientBooleanVariables); transientVariables["boundedInteger"] = cpptempl::make_data(transientBoundedIntegerVariables); transientVariables["unboundedInteger"] = cpptempl::make_data(transientUnboundedIntegerVariables); transientVariables["real"] = cpptempl::make_data(transientRealVariables); modelData["transient_variables"] = transientVariables; } template <typename ValueType, typename RewardModelType> void ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateLocations(cpptempl::data_map& modelData) { cpptempl::data_list locations; for (auto const& automaton : this->model.getAutomata()) { cpptempl::data_map locationData; uint64_t locationIndex = 0; for (auto const& location : automaton.getLocations()) { cpptempl::data_list assignments; for (auto const& assignment : location.getAssignments()) { assignments.push_back(generateAssignment(assignment)); } locationData["assignments"] = cpptempl::make_data(assignments); locationData["guard"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(getLocationVariable(automaton) == this->model.getManager().integer(locationIndex)), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); ++locationIndex; } if (!locationData["assignments"]->empty()) { locations.push_back(locationData); } } modelData["locations"] = cpptempl::make_data(locations); } template <typename ValueType, typename RewardModelType> void ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateRewards(cpptempl::data_map& modelData) { cpptempl::data_list rewards; // Extract the reward models from the program based on the names we were given. std::vector<std::reference_wrapper<storm::jani::Variable const>> rewardVariables; auto const& globalVariables = model.getGlobalVariables(); for (auto const& rewardModelName : this->options.getRewardModelNames()) { if (globalVariables.hasVariable(rewardModelName)) { rewardVariables.push_back(globalVariables.getVariable(rewardModelName)); } else { STORM_LOG_THROW(rewardModelName.empty(), storm::exceptions::InvalidArgumentException, "Cannot build unknown reward model '" << rewardModelName << "'."); STORM_LOG_THROW(globalVariables.getNumberOfRealTransientVariables() + globalVariables.getNumberOfUnboundedIntegerTransientVariables() == 1, storm::exceptions::InvalidArgumentException, "Reference to standard reward model is ambiguous."); } } // If no reward model was yet added, but there was one that was given in the options, we try to build the // standard reward model. if (rewardVariables.empty() && !this->options.getRewardModelNames().empty()) { bool foundTransientVariable = false; for (auto const& transientVariable : globalVariables.getTransientVariables()) { if (transientVariable.isUnboundedIntegerVariable() || transientVariable.isRealVariable()) { rewardVariables.push_back(transientVariable); foundTransientVariable = true; break; } } STORM_LOG_ASSERT(foundTransientVariable, "Expected to find a fitting transient variable."); } modelData["rewards"] = cpptempl::make_data(rewards); } template <typename ValueType, typename RewardModelType> cpptempl::data_list ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateLabels() { cpptempl::data_list labels; // As in JANI we can use transient boolean variable assignments in locations to identify states, we need to // create a list of boolean transient variables and the expressions that define them. for (auto const& variable : model.getGlobalVariables().getTransientVariables()) { if (variable.isBooleanVariable()) { if (this->options.isBuildAllLabelsSet() || this->options.getLabelNames().find(variable.getName()) != this->options.getLabelNames().end()) { cpptempl::data_map label; label["name"] = variable.getName(); label["predicate"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(model.getLabelExpression(variable.asBooleanVariable(), automatonToLocationVariable)), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); labels.push_back(label); } } } for (auto const& expression : this->options.getExpressionLabels()) { cpptempl::data_map label; label["name"] = expression.toString(); label["predicate"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(expression), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); labels.push_back(label); } return labels; } template <typename ValueType, typename RewardModelType> cpptempl::data_list ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateTerminalExpressions() { cpptempl::data_list terminalExpressions; for (auto const& terminalEntry : options.getTerminalStates()) { LabelOrExpression const& labelOrExpression = terminalEntry.first; if (labelOrExpression.isLabel()) { auto const& variables = model.getGlobalVariables(); STORM_LOG_THROW(variables.hasVariable(labelOrExpression.getLabel()), storm::exceptions::WrongFormatException, "Terminal label refers to unknown identifier '" << labelOrExpression.getLabel() << "'."); auto const& variable = variables.getVariable(labelOrExpression.getLabel()); STORM_LOG_THROW(variable.isBooleanVariable(), storm::exceptions::WrongFormatException, "Terminal label refers to non-boolean variable '" << variable.getName() << "."); STORM_LOG_THROW(variable.isTransient(), storm::exceptions::WrongFormatException, "Terminal label refers to non-transient variable '" << variable.getName() << "."); auto labelExpression = model.getLabelExpression(variable.asBooleanVariable(), automatonToLocationVariable); if (terminalEntry.second) { labelExpression = !labelExpression; } terminalExpressions.push_back(expressionTranslator.translate(shiftVariablesWrtLowerBound(labelExpression), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName))); } else { auto expression = terminalEntry.second ? labelOrExpression.getExpression() : !labelOrExpression.getExpression(); terminalExpressions.push_back(expressionTranslator.translate(shiftVariablesWrtLowerBound(expression), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName))); } } return terminalExpressions; } std::ostream& indent(std::ostream& out, uint64_t indentLevel) { for (uint64_t i = 0; i < indentLevel; ++i) { out << "\t"; } return out; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateSynchronizationVector(storm::jani::ParallelComposition const& parallelComposition, storm::jani::SynchronizationVector const& synchronizationVector, uint64_t synchronizationVectorIndex) { std::stringstream vectorSource; uint64_t numberOfActionInputs = synchronizationVector.getNumberOfActionInputs(); // First, we check whether we need to generate code for a) different assignment levels and b) transient variables. uint64_t position = 0; int_fast64_t lowestLevel; int_fast64_t highestLevel; bool firstDestination = true; bool generateTransient = false; for (auto const& inputActionName : synchronizationVector.getInput()) { if (!storm::jani::SynchronizationVector::isNoActionInput(inputActionName)) { storm::jani::Automaton const& automaton = model.getAutomaton(parallelComposition.getSubcomposition(position).asAutomatonComposition().getAutomatonName()); uint64_t actionIndex = model.getActionIndex(inputActionName); for (auto const& edge : automaton.getEdges()) { if (edge.getActionIndex() == actionIndex) { for (auto const& destination : edge.getDestinations()) { if (!destination.getOrderedAssignments().empty()) { if (firstDestination) { lowestLevel = destination.getOrderedAssignments().getLowestLevel(); highestLevel = destination.getOrderedAssignments().getHighestLevel(); firstDestination = false; } else { lowestLevel = std::min(lowestLevel, destination.getOrderedAssignments().getLowestLevel()); highestLevel = std::max(highestLevel, destination.getOrderedAssignments().getHighestLevel()); } } if (!generateTransient) { for (auto const& assignment : destination.getOrderedAssignments()) { if (assignment.isTransient()) { generateTransient = true; } std::set<storm::expressions::Variable> usedVariables = assignment.getAssignedExpression().getVariables(); for (auto const& variable : usedVariables) { if (transientVariables.find(variable) != transientVariables.end()) { generateTransient = true; } } } } } } } } ++position; } bool generateLevelCode = lowestLevel != highestLevel; uint64_t indentLevel = 4; indent(vectorSource, indentLevel - 4) << "void performSynchronizedDestinations_" << synchronizationVectorIndex << "(StateType const& in, StateBehaviour<IndexType, ValueType>& behaviour, StateSet<StateType>& statesToExplore, "; for (uint64_t index = 0; index < numberOfActionInputs; ++index) { vectorSource << "Destination const& destination" << index << ", "; } if (generateLevelCode) { vectorSource << "int64_t lowestLevel, int64_t highestLevel, "; } vectorSource << "Choice<IndexType, ValueType>& choice) {" << std::endl; indent(vectorSource, indentLevel + 1) << "StateType out(in);" << std::endl; indent(vectorSource, indentLevel + 1) << "TransientVariables transientIn;" << std::endl; indent(vectorSource, indentLevel + 1) << "TransientVariables transientOut;" << std::endl; if (generateLevelCode) { indent(vectorSource, indentLevel + 1) << "for (int64_t level = lowestLevel; level <= highestLevel; ++level) {" << std::endl; ++indentLevel; } for (uint64_t index = 0; index < numberOfActionInputs; ++index) { indent(vectorSource, indentLevel + 1) << "destination" << index << ".perform("; if (generateLevelCode) { vectorSource << "level, "; } vectorSource << "in, out, transientIn, transientOut);" << std::endl; } if (generateLevelCode) { --indentLevel; indent(vectorSource, indentLevel + 1) << "}" << std::endl; } indent(vectorSource, indentLevel + 1) << "IndexType outStateIndex = getOrAddIndex(out, statesToExplore);" << std::endl; indent(vectorSource, indentLevel + 1) << "choice.add(outStateIndex, "; for (uint64_t index = 0; index < numberOfActionInputs; ++index) { vectorSource << "destination" << index << ".value()"; if (index + 1 < numberOfActionInputs) { vectorSource << " * "; } } vectorSource << ");" << std::endl; indent(vectorSource, indentLevel) << "}" << std::endl << std::endl; indent(vectorSource, indentLevel) << "void performSynchronizedDestinations_" << synchronizationVectorIndex << "(StateType const& in, StateBehaviour<IndexType, ValueType>& behaviour, StateSet<StateType>& statesToExplore, "; for (uint64_t index = 0; index < numberOfActionInputs; ++index) { vectorSource << "Edge const& edge" << index << ", "; } vectorSource << "Choice<IndexType, ValueType>& choice) {" << std::endl; if (generateLevelCode) { indent(vectorSource, indentLevel + 1) << "int64_t lowestLevel; int64_t highestLevel;"; } for (uint64_t index = 0; index < numberOfActionInputs; ++index) { indent(vectorSource, indentLevel + 1 + index) << "for (auto const& destination" << index << " : edge" << index << ") {" << std::endl; if (generateLevelCode) { if (index == 0) { indent(vectorSource, indentLevel + 2 + index) << "lowestLevel = edge0.lowestLevel();" << std::endl; indent(vectorSource, indentLevel + 2 + index) << "highestLevel = edge0.highestLevel();" << std::endl; } else { indent(vectorSource, indentLevel + 2 + index) << "lowestLevel = std::min(lowestLevel, edge0.lowestLevel());" << std::endl; indent(vectorSource, indentLevel + 2 + index) << "highestLevel = std::max(highestLevel, edge0.highestLevel());" << std::endl; } } } indent(vectorSource, indentLevel + 1 + numberOfActionInputs) << "performSynchronizedDestinations_" << synchronizationVectorIndex << "(in, behaviour, statesToExplore, "; for (uint64_t index = 0; index < numberOfActionInputs; ++index) { vectorSource << "destination" << index << ", "; } if (generateLevelCode) { vectorSource << "lowestLevel, highestLevel, "; } vectorSource << "choice);" << std::endl; for (uint64_t index = 0; index < numberOfActionInputs; ++index) { indent(vectorSource, indentLevel + numberOfActionInputs - index) << "}" << std::endl; } indent(vectorSource, indentLevel) << "}" << std::endl << std::endl; for (uint64_t index = 0; index < numberOfActionInputs; ++index) { indent(vectorSource, indentLevel) << "void performSynchronizedEdges_" << synchronizationVectorIndex << "_" << index << "(StateType const& in, std::vector<std::vector<std::reference_wrapper<Edge const>>> const& edges, StateBehaviour<IndexType, ValueType>& behaviour, StateSet<StateType>& statesToExplore"; if (index > 0) { vectorSource << ", "; } for (uint64_t innerIndex = 0; innerIndex < index; ++innerIndex) { vectorSource << "Edge const& edge" << innerIndex; if (innerIndex + 1 < index) { vectorSource << ", "; } } vectorSource << ") {" << std::endl; indent(vectorSource, indentLevel + 1) << "for (auto const& edge" << index << " : edges[" << index << "]) {" << std::endl; if (index + 1 < numberOfActionInputs) { indent(vectorSource, indentLevel + 2) << "performSynchronizedEdges_" << synchronizationVectorIndex << "_" << (index + 1) << "(in, edges, behaviour, statesToExplore, "; for (uint64_t innerIndex = 0; innerIndex <= index; ++innerIndex) { vectorSource << "edge" << innerIndex; if (innerIndex + 1 <= index) { vectorSource << ", "; } } vectorSource << ");" << std::endl; } else { indent(vectorSource, indentLevel + 2) << "Choice<IndexType, ValueType>& choice = behaviour.addChoice();" << std::endl; indent(vectorSource, indentLevel + 2) << "performSynchronizedDestinations_" << synchronizationVectorIndex << "(in, behaviour, statesToExplore, "; for (uint64_t innerIndex = 0; innerIndex <= index; ++innerIndex) { vectorSource << "edge" << innerIndex << ", "; } vectorSource << " choice);" << std::endl; } indent(vectorSource, indentLevel + 1) << "}" << std::endl; indent(vectorSource, indentLevel) << "}" << std::endl << std::endl; } indent(vectorSource, indentLevel) << "void get_edges_" << synchronizationVectorIndex << "(StateType const& state, std::vector<std::reference_wrapper<Edge const>>& edges, uint64_t position) {" << std::endl; position = 0; uint64_t participatingPosition = 0; for (auto const& inputActionName : synchronizationVector.getInput()) { if (!storm::jani::SynchronizationVector::isNoActionInput(inputActionName)) { indent(vectorSource, indentLevel + 1) << "if (position == " << participatingPosition << ") {" << std::endl; storm::jani::Automaton const& automaton = model.getAutomaton(parallelComposition.getSubcomposition(position).asAutomatonComposition().getAutomatonName()); uint64_t actionIndex = model.getActionIndex(inputActionName); uint64_t edgeIndex = 0; for (auto const& edge : automaton.getEdges()) { if (edge.getActionIndex() == actionIndex) { std::string edgeName = automaton.getName() + "_" + std::to_string(edgeIndex); indent(vectorSource, indentLevel + 2) << "if (edge_enabled_" << edgeName << "(state)) {" << std::endl; indent(vectorSource, indentLevel + 3) << "edges.emplace_back(edge_" << edgeName << ");" << std::endl; indent(vectorSource, indentLevel + 2) << "}" << std::endl; } ++edgeIndex; } indent(vectorSource, indentLevel + 1) << "}" << std::endl; ++participatingPosition; } ++position; } indent(vectorSource, indentLevel) << "}" << std::endl << std::endl; indent(vectorSource, indentLevel) << "void exploreSynchronizationVector_" << synchronizationVectorIndex << "(StateType const& state, StateBehaviour<IndexType, ValueType>& behaviour, StateSet<StateType>& statesToExplore) {" << std::endl; indent(vectorSource, indentLevel + 1) << "std::vector<std::vector<std::reference_wrapper<Edge const>>> edges(" << synchronizationVector.getNumberOfActionInputs() << ");" << std::endl; participatingPosition = 0; for (auto const& input : synchronizationVector.getInput()) { if (!storm::jani::SynchronizationVector::isNoActionInput(input)) { indent(vectorSource, indentLevel + 1) << "get_edges_" << synchronizationVectorIndex << "(state, edges[" << participatingPosition << "], " << participatingPosition << ");" << std::endl; indent(vectorSource, indentLevel + 1) << "if (edges[" << participatingPosition << "].empty()) {" << std::endl; indent(vectorSource, indentLevel + 2) << "return;" << std::endl; indent(vectorSource, indentLevel + 1) << "}" << std::endl; ++participatingPosition; } } indent(vectorSource, indentLevel + 1) << "performSynchronizedEdges_" << synchronizationVectorIndex << "_0(state, edges, behaviour, statesToExplore);" << std::endl; indent(vectorSource, indentLevel) << "}" << std::endl << std::endl; cpptempl::data_map vector; vector["functions"] = vectorSource.str(); vector["index"] = asString(synchronizationVectorIndex); return vector; } template <typename ValueType, typename RewardModelType> void ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateEdges(cpptempl::data_map& modelData) { STORM_LOG_THROW(model.hasStandardCompliantComposition(), storm::exceptions::WrongFormatException, "Model builder only supports non-nested parallel compositions."); cpptempl::data_list nonSynchronizingEdges; cpptempl::data_list synchronizingEdges; cpptempl::data_list vectors; storm::jani::Composition const& topLevelComposition = model.getSystemComposition(); if (topLevelComposition.isAutomatonComposition()) { storm::jani::Automaton const& automaton = model.getAutomaton(topLevelComposition.asAutomatonComposition().getAutomatonName()); uint64_t edgeIndex = 0; for (auto const& edge : automaton.getEdges()) { nonSynchronizingEdges.push_back(generateEdge(automaton, edgeIndex, edge)); ++edgeIndex; } } else { STORM_LOG_ASSERT(topLevelComposition.isParallelComposition(), "Expected parallel composition."); storm::jani::ParallelComposition const& parallelComposition = topLevelComposition.asParallelComposition(); #ifndef NDEBUG for (auto const& composition : parallelComposition.getSubcompositions()) { STORM_LOG_ASSERT(composition->isAutomatonComposition(), "Expected flat parallel composition."); } #endif std::vector<std::set<uint64_t>> synchronizingActions(parallelComposition.getNumberOfSubcompositions()); uint64_t synchronizationVectorIndex = 0; for (auto const& synchronizationVector : parallelComposition.getSynchronizationVectors()) { // If the synchronization vector has at most one action input, there is no synchronization going on. if (synchronizationVector.getNumberOfActionInputs() <= 1) { continue; } bool createVector = true; uint64_t position = 0; for (auto const& inputActionName : synchronizationVector.getInput()) { if (!storm::jani::SynchronizationVector::isNoActionInput(inputActionName)) { uint64_t actionIndex = model.getActionIndex(inputActionName); synchronizingActions[position].insert(actionIndex); storm::jani::Automaton const& automaton = model.getAutomaton(parallelComposition.getSubcomposition(position).asAutomatonComposition().getAutomatonName()); bool hasParticipatingEdge = false; for (auto const& edge : automaton.getEdges()) { if (edge.getActionIndex() == actionIndex) { hasParticipatingEdge = true; break; } } if (!hasParticipatingEdge) { createVector = false; } } ++position; } if (createVector) { cpptempl::data_map vector = generateSynchronizationVector(parallelComposition, synchronizationVector, synchronizationVectorIndex); vectors.push_back(vector); } ++synchronizationVectorIndex; } uint64_t position = 0; for (auto const& composition : parallelComposition.getSubcompositions()) { storm::jani::Automaton const& automaton = model.getAutomaton(composition->asAutomatonComposition().getAutomatonName()); // Add all edges with an action index that is not mentioned in any synchronization vector as // non-synchronizing edges. uint64_t edgeIndex = 0; for (auto const& edge : automaton.getEdges()) { if (synchronizingActions[position].find(edge.getActionIndex()) != synchronizingActions[position].end()) { synchronizingEdges.push_back(generateEdge(automaton, edgeIndex, edge)); } else { nonSynchronizingEdges.push_back(generateEdge(automaton, edgeIndex, edge)); } ++edgeIndex; } ++position; } } modelData["nonsynch_edges"] = cpptempl::make_data(nonSynchronizingEdges); modelData["synch_edges"] = cpptempl::make_data(synchronizingEdges); modelData["synch_vectors"] = cpptempl::make_data(vectors); } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateEdge(storm::jani::Automaton const& automaton, uint64_t edgeIndex, storm::jani::Edge const& edge) { cpptempl::data_map edgeData; std::set<storm::expressions::Variable> transientVariablesInEdge; cpptempl::data_list edgeAssignments; for (auto const& assignment : edge.getAssignments()) { transientVariablesInEdge.insert(assignment.getExpressionVariable()); std::set<storm::expressions::Variable> usedVariables = assignment.getAssignedExpression().getVariables(); for (auto const& variable : usedVariables) { if (transientVariables.find(variable) != transientVariables.end()) { transientVariablesInEdge.insert(variable); } } edgeAssignments.push_back(generateAssignment(assignment)); } cpptempl::data_list destinations; uint64_t destinationIndex = 0; std::set<storm::expressions::Variable> transientVariablesInDestinations; for (auto const& destination : edge.getDestinations()) { destinations.push_back(generateDestination(destinationIndex, destination)); for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) { if (assignment.isTransient()) { transientVariablesInDestinations.insert(assignment.getExpressionVariable()); } std::set<storm::expressions::Variable> usedVariables = assignment.getAssignedExpression().getVariables(); for (auto const& variable : usedVariables) { if (transientVariables.find(variable) != transientVariables.end()) { transientVariablesInDestinations.insert(variable); } } } ++destinationIndex; } edgeData["guard"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(edge.getGuard()), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); edgeData["destinations"] = cpptempl::make_data(destinations); edgeData["name"] = automaton.getName() + "_" + std::to_string(edgeIndex); edgeData["transient_assignments"] = cpptempl::make_data(edgeAssignments); cpptempl::data_list transientVariablesInDestinationsData; for (auto const& variable : transientVariablesInDestinations) { transientVariablesInDestinationsData.push_back(getVariableName(variable)); } edgeData["transient_variables_in_destinations"] = cpptempl::make_data(transientVariablesInDestinationsData); cpptempl::data_list transientVariablesInEdgeData; for (auto const& variable : transientVariablesInEdge) { transientVariablesInEdgeData.push_back(getVariableName(variable)); } edgeData["transient_variables_in_edge"] = cpptempl::make_data(transientVariablesInEdgeData); return edgeData; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateDestination(uint64_t destinationIndex, storm::jani::EdgeDestination const& destination) { cpptempl::data_map destinationData; cpptempl::data_list levels = generateLevels(destination.getOrderedAssignments()); destinationData["name"] = asString(destinationIndex); destinationData["levels"] = cpptempl::make_data(levels); destinationData["value"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(destination.getProbability()), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName, "double")); if (destination.getOrderedAssignments().empty()) { destinationData["lowestLevel"] = "0"; destinationData["highestLevel"] = "0"; } else { destinationData["lowestLevel"] = asString(destination.getOrderedAssignments().getLowestLevel()); destinationData["highestLevel"] = asString(destination.getOrderedAssignments().getHighestLevel()); } return destinationData; } template <typename ValueType, typename RewardModelType> cpptempl::data_list ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateLevels(storm::jani::OrderedAssignments const& orderedAssignments) { cpptempl::data_list levels; auto const& assignments = orderedAssignments.getAllAssignments(); if (!assignments.empty()) { int_fast64_t currentLevel = assignments.begin()->getLevel(); cpptempl::data_list nonTransientAssignmentData; cpptempl::data_list transientAssignmentData; for (auto const& assignment : assignments) { if (assignment.getLevel() != currentLevel) { cpptempl::data_map level; level["non_transient_assignments"] = cpptempl::make_data(nonTransientAssignmentData); level["transient_assignments"] = cpptempl::make_data(transientAssignmentData); level["index"] = asString(currentLevel); levels.push_back(level); nonTransientAssignmentData = cpptempl::data_list(); transientAssignmentData = cpptempl::data_list(); currentLevel = assignment.getLevel(); } if (assignment.isTransient()) { transientAssignmentData.push_back(generateAssignment(assignment)); } else { nonTransientAssignmentData.push_back(generateAssignment(assignment)); } } // Close the last (open) level. cpptempl::data_map level; level["non_transient_assignments"] = cpptempl::make_data(nonTransientAssignmentData); level["transient_assignments"] = cpptempl::make_data(transientAssignmentData); level["index"] = asString(currentLevel); levels.push_back(level); } return levels; } template <typename ValueType, typename RewardModelType> template <typename ValueTypePrime> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateAssignment(storm::jani::Variable const& variable, ValueTypePrime value) const { cpptempl::data_map result; result["variable"] = getVariableName(variable.getExpressionVariable()); result["value"] = asString(value); return result; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateLocationAssignment(storm::jani::Automaton const& automaton, uint64_t value) const { cpptempl::data_map result; result["variable"] = getVariableName(getLocationVariable(automaton)); result["value"] = asString(value); return result; } template <typename ValueType, typename RewardModelType> cpptempl::data_map ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::generateAssignment(storm::jani::Assignment const& assignment) { cpptempl::data_map result; result["variable"] = getVariableName(assignment.getExpressionVariable()); auto lowerBoundIt = lowerBounds.find(assignment.getExpressionVariable()); if (lowerBoundIt != lowerBounds.end()) { if (lowerBoundIt->second < 0) { result["value"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(assignment.getAssignedExpression()) + model.getManager().integer(lowerBoundIt->second), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); } else if (lowerBoundIt->second == 0) { result["value"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(assignment.getAssignedExpression()), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); } else { result["value"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(assignment.getAssignedExpression()) - model.getManager().integer(lowerBoundIt->second), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); } } else { result["value"] = expressionTranslator.translate(shiftVariablesWrtLowerBound(assignment.getAssignedExpression()), storm::expressions::ToCppTranslationOptions(variablePrefixes, variableToName)); } return result; } template <typename ValueType, typename RewardModelType> std::string const& ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::getVariableName(storm::expressions::Variable const& variable) const { return variableToName.at(variable); } template <typename ValueType, typename RewardModelType> std::string const& ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::registerVariable(storm::expressions::Variable const& variable, bool transient) { std::string variableName; // Technically, we would need to catch all keywords here... if (variable.getName() == "default") { variableName = "__default"; } else { variableName = variable.getName(); } variableToName[variable] = variableName; if (transient) { transientVariables.insert(variable); variablePrefixes[variable] = "transientIn."; } else { nontransientVariables.insert(variable); variablePrefixes[variable] = "in."; } return variableToName[variable] ; } template <typename ValueType, typename RewardModelType> storm::expressions::Variable const& ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::getLocationVariable(storm::jani::Automaton const& automaton) const { return automatonToLocationVariable.at(automaton.getName()); } template <typename ValueType, typename RewardModelType> std::string ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::asString(bool value) const { std::stringstream out; out << std::boolalpha << value; return out.str(); } template <typename ValueType, typename RewardModelType> storm::expressions::Expression ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::shiftVariablesWrtLowerBound(storm::expressions::Expression const& expression) { return expression.substitute(lowerBoundShiftSubstitution); } template <typename ValueType, typename RewardModelType> template <typename ValueTypePrime> std::string ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::asString(ValueTypePrime value) const { return std::to_string(value); } template <typename ValueType, typename RewardModelType> boost::optional<std::string> ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::execute(std::string command) { auto start = std::chrono::high_resolution_clock::now(); char buffer[128]; std::stringstream output; command += " 2>&1"; std::cout << "executing " << command << std::endl; std::unique_ptr<FILE> pipe(popen(command.c_str(), "r")); STORM_LOG_THROW(pipe, storm::exceptions::InvalidStateException, "Call to popen failed."); while (!feof(pipe.get())) { if (fgets(buffer, 128, pipe.get()) != nullptr) output << buffer; } int result = pclose(pipe.get()); pipe.release(); auto end = std::chrono::high_resolution_clock::now(); std::cout << "Executing command took " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl; if (WEXITSTATUS(result) == 0) { return boost::none; } else { return "Executing command failed. Got response: " + output.str(); } } template <typename ValueType, typename RewardModelType> void ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::createBuilder(boost::filesystem::path const& dynamicLibraryPath) { jitBuilderGetFunction = boost::dll::import_alias<typename ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::CreateFunctionType>(dynamicLibraryPath, "create_builder"); builder = std::unique_ptr<JitModelBuilderInterface<IndexType, ValueType>>(jitBuilderGetFunction(modelComponentsBuilder)); } template <typename ValueType, typename RewardModelType> boost::filesystem::path ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::writeSourceToTemporaryFile(std::string const& source) { boost::filesystem::path temporaryFile = boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%.cpp"); std::ofstream out(temporaryFile.native()); out << source << std::endl; out.close(); return temporaryFile; } template <typename ValueType, typename RewardModelType> boost::filesystem::path ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::compileSourceToSharedLibrary(boost::filesystem::path const& sourceFile) { std::string sourceFilename = boost::filesystem::absolute(sourceFile).string(); auto dynamicLibraryPath = sourceFile; dynamicLibraryPath += DYLIB_EXTENSION; std::string dynamicLibraryFilename = boost::filesystem::absolute(dynamicLibraryPath).string(); std::string command = CXX_COMPILER + " " + sourceFilename + " " + COMPILER_FLAGS + " -I" + STORM_ROOT + " -I" + STORM_ROOT + "/build_xcode/include -I" + L3PP_ROOT + " -I" + BOOST_ROOT + " -I" + GMP_ROOT + "/include -I" + CARL_ROOT + "/src -I" + CLN_ROOT + "/include -I" + GINAC_ROOT + "/include -o " + dynamicLibraryFilename; boost::optional<std::string> error = execute(command); if (error) { boost::filesystem::remove(sourceFile); STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Compiling shared library failed. Error: " << error.get()); } return dynamicLibraryPath; } template <typename ValueType, typename RewardModelType> std::shared_ptr<storm::models::sparse::Model<ValueType, RewardModelType>> ExplicitJitJaniModelBuilder<ValueType, RewardModelType>::build() { // (1) generate the source code of the shared library std::string source; try { source = createSourceCode(); } catch (std::exception const& e) { STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "The model could not be successfully built (error: " << e.what() << ")."); } std::cout << "created source code: " << source << std::endl; // (2) write the source code to a temporary file boost::filesystem::path temporarySourceFile = writeSourceToTemporaryFile(source); std::cout << "wrote source to file " << temporarySourceFile.native() << std::endl; // (3) compile the shared library boost::filesystem::path dynamicLibraryPath = compileSourceToSharedLibrary(temporarySourceFile); std::cout << "successfully compiled shared library" << std::endl; // (4) remove the source code we just compiled boost::filesystem::remove(temporarySourceFile); // (5) create the loader from the shared library createBuilder(dynamicLibraryPath); // (6) execute the function in the shared lib auto start = std::chrono::high_resolution_clock::now(); auto sparseModel = builder->build(); auto end = std::chrono::high_resolution_clock::now(); std::cout << "Building model took " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms." << std::endl; // (7) delete the shared library boost::filesystem::remove(dynamicLibraryPath); // Return the constructed model. return std::shared_ptr<storm::models::sparse::Model<ValueType, RewardModelType>>(sparseModel); } template class ExplicitJitJaniModelBuilder<double, storm::models::sparse::StandardRewardModel<double>>; template class ExplicitJitJaniModelBuilder<storm::RationalNumber, storm::models::sparse::StandardRewardModel<storm::RationalNumber>>; template class ExplicitJitJaniModelBuilder<storm::RationalFunction, storm::models::sparse::StandardRewardModel<storm::RationalFunction>>; } } }