You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
623 lines
25 KiB
623 lines
25 KiB
#include "storm/storage/jani/Automaton.h"
|
|
|
|
#include "storm/storage/jani/Edge.h"
|
|
#include "storm/storage/jani/TemplateEdge.h"
|
|
#include "storm/storage/jani/Location.h"
|
|
#include "storm/storage/jani/expressions/JaniExpressionSubstitutionVisitor.h"
|
|
|
|
#include "storm/utility/macros.h"
|
|
#include "storm/exceptions/WrongFormatException.h"
|
|
#include "storm/exceptions/InvalidArgumentException.h"
|
|
#include "storm/exceptions/InvalidTypeException.h"
|
|
#include "storm/exceptions/NotSupportedException.h"
|
|
|
|
namespace storm {
|
|
namespace jani {
|
|
|
|
|
|
|
|
Automaton::Automaton(std::string const& name, storm::expressions::Variable const& locationExpressionVariable) : name(name), locationExpressionVariable(locationExpressionVariable) {
|
|
// Add a sentinel element to the mapping from locations to starting indices.
|
|
locationToStartingIndex.push_back(0);
|
|
}
|
|
|
|
std::string const& Automaton::getName() const {
|
|
return name;
|
|
}
|
|
|
|
Variable const& Automaton::addVariable(Variable const &variable) {
|
|
if (variable.isBooleanVariable()) {
|
|
return addVariable(variable.asBooleanVariable());
|
|
} else if (variable.isBoundedIntegerVariable()) {
|
|
return addVariable(variable.asBoundedIntegerVariable());
|
|
} else if (variable.isUnboundedIntegerVariable()) {
|
|
return addVariable(variable.asUnboundedIntegerVariable());
|
|
} else if (variable.isRealVariable()) {
|
|
return addVariable(variable.asRealVariable());
|
|
} else if (variable.isArrayVariable()) {
|
|
return addVariable(variable.asArrayVariable());
|
|
} else if (variable.isClockVariable()) {
|
|
return addVariable(variable.asClockVariable());
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidTypeException, "Variable has invalid type.");
|
|
}
|
|
}
|
|
|
|
BooleanVariable const& Automaton::addVariable(BooleanVariable const& variable) {
|
|
return variables.addVariable(variable);
|
|
}
|
|
|
|
BoundedIntegerVariable const& Automaton::addVariable(BoundedIntegerVariable const& variable) {
|
|
return variables.addVariable(variable);
|
|
}
|
|
|
|
UnboundedIntegerVariable const& Automaton::addVariable(UnboundedIntegerVariable const& variable) {
|
|
return variables.addVariable(variable);
|
|
}
|
|
|
|
RealVariable const& Automaton::addVariable(RealVariable const& variable) {
|
|
return variables.addVariable(variable);
|
|
}
|
|
|
|
ArrayVariable const& Automaton::addVariable(ArrayVariable const& variable) {
|
|
return variables.addVariable(variable);
|
|
}
|
|
|
|
ClockVariable const& Automaton::addVariable(ClockVariable const& variable) {
|
|
return variables.addVariable(variable);
|
|
}
|
|
|
|
bool Automaton::hasVariable(std::string const& name) const {
|
|
return variables.hasVariable(name);
|
|
}
|
|
|
|
VariableSet& Automaton::getVariables() {
|
|
return variables;
|
|
}
|
|
|
|
VariableSet const& Automaton::getVariables() const {
|
|
return variables;
|
|
}
|
|
|
|
std::set<storm::expressions::Variable> Automaton::getAllExpressionVariables() const {
|
|
std::set<storm::expressions::Variable> result;
|
|
for (auto const& variable : this->getVariables()) {
|
|
result.insert(variable.getExpressionVariable());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool Automaton::hasTransientVariable() const {
|
|
return variables.hasTransientVariable();
|
|
}
|
|
|
|
FunctionDefinition const& Automaton::addFunctionDefinition(FunctionDefinition const& functionDefinition) {
|
|
auto insertionRes = functionDefinitions.emplace(functionDefinition.getName(), functionDefinition);
|
|
STORM_LOG_THROW(insertionRes.second, storm::exceptions::InvalidArgumentException, " a function with the name " << functionDefinition.getName() << " already exists in this automaton (" << this->getName() << ")");
|
|
return insertionRes.first->second;
|
|
}
|
|
|
|
std::unordered_map<std::string, FunctionDefinition> const& Automaton::getFunctionDefinitions() const {
|
|
return functionDefinitions;
|
|
}
|
|
|
|
std::unordered_map<std::string, FunctionDefinition>& Automaton::getFunctionDefinitions() {
|
|
return functionDefinitions;
|
|
}
|
|
|
|
bool Automaton::hasLocation(std::string const& name) const {
|
|
return locationToIndex.find(name) != locationToIndex.end();
|
|
}
|
|
|
|
std::vector<Location> const& Automaton::getLocations() const {
|
|
return locations;
|
|
}
|
|
|
|
std::vector<Location>& Automaton::getLocations() {
|
|
return locations;
|
|
}
|
|
|
|
Location const& Automaton::getLocation(uint64_t index) const {
|
|
return locations[index];
|
|
}
|
|
|
|
Location& Automaton::getLocation(uint64_t index) {
|
|
return locations[index];
|
|
}
|
|
|
|
uint64_t Automaton::addLocation(Location const& location) {
|
|
STORM_LOG_THROW(!this->hasLocation(location.getName()), storm::exceptions::WrongFormatException, "Cannot add location with name '" << location.getName() << "', because a location with this name already exists.");
|
|
locationToIndex.emplace(location.getName(), locations.size());
|
|
locations.push_back(location);
|
|
locationToStartingIndex.push_back(edges.size());
|
|
return locations.size() - 1;
|
|
}
|
|
|
|
uint64_t Automaton::getLocationIndex(std::string const& name) const {
|
|
assert(hasLocation(name));
|
|
return locationToIndex.at(name);
|
|
}
|
|
|
|
void Automaton::addInitialLocation(std::string const& name) {
|
|
auto it = locationToIndex.find(name);
|
|
STORM_LOG_THROW(it != locationToIndex.end(), storm::exceptions::InvalidArgumentException, "Cannot make unknown location '" << name << "' the initial location.");
|
|
return addInitialLocation(it->second);
|
|
}
|
|
|
|
void Automaton::addInitialLocation(uint64_t index) {
|
|
STORM_LOG_THROW(index < locations.size(), storm::exceptions::InvalidArgumentException, "Cannot make location with index " << index << " initial: out of bounds.");
|
|
initialLocationIndices.insert(index);
|
|
}
|
|
|
|
std::set<uint64_t> const& Automaton::getInitialLocationIndices() const {
|
|
return initialLocationIndices;
|
|
}
|
|
|
|
std::map<uint64_t, std::string> Automaton::buildIdToLocationNameMap() const {
|
|
std::map<uint64_t, std::string> mapping;
|
|
uint64_t i = 0;
|
|
for(auto const& loc : locations) {
|
|
mapping[i] = loc.getName();
|
|
++i;
|
|
}
|
|
return mapping;
|
|
}
|
|
|
|
storm::expressions::Variable const& Automaton::getLocationExpressionVariable() const {
|
|
return locationExpressionVariable;
|
|
}
|
|
|
|
Edge const& Automaton::getEdge(uint64_t index) const {
|
|
return edges.getConcreteEdges()[index];
|
|
}
|
|
|
|
Automaton::Edges Automaton::getEdgesFromLocation(std::string const& name) {
|
|
auto it = locationToIndex.find(name);
|
|
STORM_LOG_THROW(it != locationToIndex.end(), storm::exceptions::InvalidArgumentException, "Cannot retrieve edges from unknown location '" << name << ".");
|
|
return getEdgesFromLocation(it->second);
|
|
}
|
|
|
|
Automaton::Edges Automaton::getEdgesFromLocation(uint64_t index) {
|
|
auto it = edges.begin();
|
|
std::advance(it, locationToStartingIndex[index]);
|
|
auto ite = edges.begin();
|
|
std::advance(ite, locationToStartingIndex[index + 1]);
|
|
return Edges(it, ite);
|
|
}
|
|
|
|
Automaton::ConstEdges Automaton::getEdgesFromLocation(std::string const& name) const {
|
|
auto it = locationToIndex.find(name);
|
|
STORM_LOG_THROW(it != locationToIndex.end(), storm::exceptions::InvalidArgumentException, "Cannot retrieve edges from unknown location '" << name << ".");
|
|
return getEdgesFromLocation(it->second);
|
|
}
|
|
|
|
Automaton::ConstEdges Automaton::getEdgesFromLocation(uint64_t index) const {
|
|
auto it = edges.begin();
|
|
std::advance(it, locationToStartingIndex[index]);
|
|
auto ite = edges.begin();
|
|
std::advance(ite, locationToStartingIndex[index + 1]);
|
|
return ConstEdges(it, ite);
|
|
}
|
|
|
|
Automaton::Edges Automaton::getEdgesFromLocation(uint64_t locationIndex, uint64_t actionIndex) {
|
|
typedef std::vector<Edge>::iterator ForwardIt;
|
|
|
|
// Perform binary search for start of edges with the given action index.
|
|
auto first = edges.begin();
|
|
std::advance(first, locationToStartingIndex[locationIndex]);
|
|
auto last = edges.begin();
|
|
std::advance(last, locationToStartingIndex[locationIndex + 1]);
|
|
typename std::iterator_traits<ForwardIt>::difference_type count, step;
|
|
count = std::distance(first, last);
|
|
|
|
ForwardIt it1;
|
|
while (count > 0) {
|
|
it1 = first;
|
|
step = count / 2;
|
|
std::advance(it1, step);
|
|
if (it1->getActionIndex() < actionIndex) {
|
|
first = ++it1;
|
|
count -= step + 1;
|
|
}
|
|
else {
|
|
count = step;
|
|
}
|
|
}
|
|
it1 = first;
|
|
|
|
// If there is no such edge, we can return now.
|
|
if (it1 != last && it1->getActionIndex() > actionIndex) {
|
|
return Edges(last, last);
|
|
}
|
|
|
|
// Otherwise, perform a binary search for the end of the edges with the given action index.
|
|
count = std::distance(it1,last);
|
|
|
|
ForwardIt it2;
|
|
while (count > 0) {
|
|
it2 = it1;
|
|
step = count / 2;
|
|
std::advance(it2, step);
|
|
if (!(actionIndex < it2->getActionIndex())) {
|
|
first = ++it2;
|
|
count -= step + 1;
|
|
} else count = step;
|
|
}
|
|
it2 = first;
|
|
|
|
return Edges(it1, it2);
|
|
}
|
|
|
|
Automaton::ConstEdges Automaton::getEdgesFromLocation(uint64_t locationIndex, uint64_t actionIndex) const {
|
|
typedef std::vector<Edge>::const_iterator ForwardIt;
|
|
|
|
// Perform binary search for start of edges with the given action index.
|
|
auto first = edges.begin();
|
|
std::advance(first, locationToStartingIndex[locationIndex]);
|
|
auto last = edges.begin();
|
|
std::advance(last, locationToStartingIndex[locationIndex + 1]);
|
|
typename std::iterator_traits<ForwardIt>::difference_type count, step;
|
|
count = std::distance(first, last);
|
|
|
|
ForwardIt it1;
|
|
while (count > 0) {
|
|
it1 = first;
|
|
step = count / 2;
|
|
std::advance(it1, step);
|
|
if (it1->getActionIndex() < actionIndex) {
|
|
first = ++it1;
|
|
count -= step + 1;
|
|
}
|
|
else {
|
|
count = step;
|
|
}
|
|
}
|
|
it1 = first;
|
|
|
|
// If there is no such edge, we can return now.
|
|
if (it1 != last && it1->getActionIndex() > actionIndex) {
|
|
return ConstEdges(last, last);
|
|
}
|
|
|
|
// Otherwise, perform a binary search for the end of the edges with the given action index.
|
|
count = std::distance(it1,last);
|
|
|
|
ForwardIt it2;
|
|
while (count > 0) {
|
|
it2 = first;
|
|
step = count / 2;
|
|
std::advance(it2, step);
|
|
if (!(actionIndex < it2->getActionIndex())) {
|
|
first = ++it2;
|
|
count -= step + 1;
|
|
} else count = step;
|
|
}
|
|
it2 = first;
|
|
|
|
return ConstEdges(it1, it2);
|
|
}
|
|
|
|
EdgeContainer const& Automaton::getEdgeContainer() const {
|
|
return edges;
|
|
}
|
|
|
|
EdgeContainer& Automaton::getEdgeContainer() {
|
|
return edges;
|
|
}
|
|
|
|
void Automaton::addEdge(Edge const& edge) {
|
|
STORM_LOG_THROW(edge.getSourceLocationIndex() < locations.size(), storm::exceptions::InvalidArgumentException, "Cannot add edge with unknown source location index '" << edge.getSourceLocationIndex() << "'.");
|
|
assert(validate());
|
|
|
|
edges.insertEdge(edge, locationToStartingIndex[edge.getSourceLocationIndex()], locationToStartingIndex[edge.getSourceLocationIndex() + 1]);
|
|
// Update the set of action indices of this automaton.
|
|
actionIndices.insert(edge.getActionIndex());
|
|
|
|
// Now update the starting indices of all subsequent locations.
|
|
for (uint64_t locationIndex = edge.getSourceLocationIndex() + 1; locationIndex < locationToStartingIndex.size(); ++locationIndex) {
|
|
++locationToStartingIndex[locationIndex];
|
|
}
|
|
}
|
|
|
|
std::vector<Edge>& Automaton::getEdges() {
|
|
return edges.getConcreteEdges();
|
|
}
|
|
|
|
std::vector<Edge> const& Automaton::getEdges() const {
|
|
return edges.getConcreteEdges();
|
|
}
|
|
|
|
std::set<uint64_t> Automaton::getActionIndices() const {
|
|
return edges.getActionIndices();
|
|
}
|
|
|
|
uint64_t Automaton::getNumberOfLocations() const {
|
|
return locations.size();
|
|
}
|
|
|
|
uint64_t Automaton::getNumberOfEdges() const {
|
|
return edges.size();
|
|
}
|
|
|
|
bool Automaton::hasRestrictedInitialStates() const {
|
|
if (!hasInitialStatesRestriction()) {
|
|
return false;
|
|
}
|
|
if (getInitialStatesRestriction().containsVariables()) {
|
|
return true;
|
|
} else {
|
|
return !getInitialStatesRestriction().evaluateAsBool();
|
|
}
|
|
}
|
|
|
|
bool Automaton::hasInitialStatesRestriction() const {
|
|
return initialStatesRestriction.isInitialized();
|
|
}
|
|
|
|
bool Automaton::hasNonTrivialInitialStates() const {
|
|
if (this->hasInitialStatesRestriction() && !this->getInitialStatesRestriction().isTrue()) {
|
|
return true;
|
|
}
|
|
|
|
for (auto const& variable : this->getVariables()) {
|
|
if (variable.hasInitExpression() && !variable.isTransient()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
storm::expressions::Expression const& Automaton::getInitialStatesRestriction() const {
|
|
return initialStatesRestriction;
|
|
}
|
|
|
|
void Automaton::setInitialStatesRestriction(storm::expressions::Expression const& initialStatesRestriction) {
|
|
this->initialStatesRestriction = initialStatesRestriction;
|
|
}
|
|
|
|
storm::expressions::Expression Automaton::getInitialStatesExpression() const {
|
|
storm::expressions::Expression result;
|
|
|
|
// Add initial state restriction if there is one.
|
|
if (this->hasInitialStatesRestriction() && !this->getInitialStatesRestriction().isTrue()) {
|
|
result = this->getInitialStatesRestriction();
|
|
}
|
|
|
|
// Add the expressions for all non-transient variables that have initial expressions.
|
|
for (auto const& variable : this->getVariables()) {
|
|
if (variable.isTransient()) {
|
|
continue;
|
|
}
|
|
|
|
if (variable.hasInitExpression()) {
|
|
storm::expressions::Expression newInitExpression = variable.isBooleanVariable() ? storm::expressions::iff(variable.getExpressionVariable(), variable.getInitExpression()) : variable.getExpressionVariable() == variable.getInitExpression();
|
|
if (result.isInitialized()) {
|
|
result = result && newInitExpression;
|
|
} else {
|
|
result = newInitExpression;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool Automaton::hasTrivialInitialStatesExpression() const {
|
|
if (this->hasInitialStatesRestriction()) {
|
|
return false;
|
|
}
|
|
|
|
bool result = true;
|
|
for (auto const& variable : this->getVariables()) {
|
|
if (variable.isTransient()) {
|
|
continue;
|
|
}
|
|
|
|
result &= variable.hasInitExpression();
|
|
|
|
if (!result) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool Automaton::hasEdgeLabeledWithActionIndex(uint64_t actionIndex) const {
|
|
return actionIndices.find(actionIndex) != actionIndices.end();
|
|
}
|
|
|
|
std::vector<storm::expressions::Expression> Automaton::getAllRangeExpressions() const {
|
|
std::vector<storm::expressions::Expression> result;
|
|
for (auto const& variable : this->getVariables().getBoundedIntegerVariables()) {
|
|
result.push_back(variable.getRangeExpression());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Automaton::substitute(std::map<storm::expressions::Variable, storm::expressions::Expression> const& substitution) {
|
|
for (auto& functionDefinition : this->getFunctionDefinitions()) {
|
|
functionDefinition.second.substitute(substitution);
|
|
}
|
|
for (auto& variable : this->getVariables().getBoundedIntegerVariables()) {
|
|
variable.substitute(substitution);
|
|
}
|
|
for (auto& variable : this->getVariables().getArrayVariables()) {
|
|
variable.substitute(substitution);
|
|
}
|
|
for (auto& variable : this->getVariables().getClockVariables()) {
|
|
variable.substitute(substitution);
|
|
}
|
|
|
|
for (auto& location : this->getLocations()) {
|
|
location.substitute(substitution);
|
|
}
|
|
|
|
if (hasInitialStatesRestriction()) {
|
|
this->setInitialStatesRestriction(substituteJaniExpression(this->getInitialStatesRestriction(), substitution));
|
|
}
|
|
|
|
edges.substitute(substitution);
|
|
}
|
|
void Automaton::registerTemplateEdge(std::shared_ptr<TemplateEdge> const& te) {
|
|
edges.insertTemplateEdge(te);
|
|
}
|
|
|
|
void Automaton::changeAssignmentVariables(std::map<Variable const*, std::reference_wrapper<Variable const>> const& remapping) {
|
|
for (auto& location : locations) {
|
|
location.changeAssignmentVariables(remapping);
|
|
}
|
|
edges.changeAssignmentVariables(remapping);
|
|
}
|
|
|
|
void Automaton::finalize(Model const& containingModel) {
|
|
//simplifyIndexedAssignments();
|
|
edges.finalize(containingModel);
|
|
}
|
|
|
|
bool Automaton::containsVariablesOnlyInProbabilitiesOrTransientAssignments(std::set<storm::expressions::Variable> const& variables) const {
|
|
// Check initial states restriction expression.
|
|
if (this->hasInitialStatesRestriction()) {
|
|
if (this->getInitialStatesRestriction().containsVariable(variables)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check global variable definitions.
|
|
if (this->getVariables().containsVariablesInBoundExpressionsOrInitialValues(variables)) {
|
|
return false;
|
|
}
|
|
|
|
// Check edges.
|
|
for (auto const& edge : edges) {
|
|
if (edge.usesVariablesInNonTransientAssignments(variables)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Automaton::pushEdgeAssignmentsToDestinations() {
|
|
edges.pushAssignmentsToDestinations();
|
|
}
|
|
|
|
void Automaton::pushTransientRealLocationAssignmentsToEdges() {
|
|
std::set<std::shared_ptr<storm::jani::TemplateEdge>> encounteredTemplateEdges;
|
|
|
|
for (uint64_t locationIndex = 0; locationIndex < locations.size(); ++locationIndex) {
|
|
auto& location = locations[locationIndex];
|
|
auto edges = this->getEdgesFromLocation(locationIndex);
|
|
|
|
storm::jani::Location newLocation(location.getName());
|
|
bool createNewLocation = true;
|
|
for (auto& edge : edges) {
|
|
STORM_LOG_THROW(encounteredTemplateEdges.find(edge.getTemplateEdge()) == encounteredTemplateEdges.end(), storm::exceptions::NotSupportedException, "Pushing location assignments to edges is only supported for automata with unique template edges.");
|
|
|
|
auto& templateEdge = edge.getTemplateEdge();
|
|
encounteredTemplateEdges.insert(templateEdge);
|
|
|
|
for (auto const& assignment : location.getAssignments().getTransientAssignments()) {
|
|
if (assignment.getVariable().isTransient() && assignment.getVariable().isRealVariable()) {
|
|
templateEdge->addTransientAssignment(assignment, true);
|
|
} else if (createNewLocation) {
|
|
newLocation.addTransientAssignment(assignment);
|
|
}
|
|
}
|
|
|
|
if (createNewLocation) {
|
|
createNewLocation = false;
|
|
}
|
|
}
|
|
|
|
location = std::move(newLocation);
|
|
}
|
|
}
|
|
|
|
bool Automaton::hasTransientEdgeDestinationAssignments() const {
|
|
for (auto const& edge : this->getEdges()) {
|
|
if (edge.hasTransientEdgeDestinationAssignments()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Automaton::liftTransientEdgeDestinationAssignments(int64_t maxLevel) {
|
|
edges.liftTransientDestinationAssignments(maxLevel);
|
|
}
|
|
|
|
bool Automaton::validate() const {
|
|
assert(locationToStartingIndex.size() == locations.size() + 1);
|
|
for(uint64_t i = 0; i < locations.size(); i++) {
|
|
assert(locationToStartingIndex[i] <= locationToStartingIndex[i+1]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Automaton::usesAssignmentLevels(bool onlyTransient) const {
|
|
return edges.usesAssignmentLevels(onlyTransient);
|
|
}
|
|
|
|
bool Automaton::isLinear() const {
|
|
bool result = true;
|
|
|
|
for (auto const& location : this->getLocations()) {
|
|
result &= location.isLinear();
|
|
}
|
|
if (result) {
|
|
result &= edges.isLinear();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Automaton::restrictToEdges(boost::container::flat_set<uint_fast64_t> const& edgeIndices) {
|
|
std::vector<Edge> oldEdges = this->edges.getConcreteEdges();
|
|
|
|
this->edges.clearConcreteEdges();
|
|
actionIndices.clear();
|
|
for (auto& e : locationToStartingIndex) {
|
|
e = 0;
|
|
}
|
|
|
|
for (auto const& index : edgeIndices) {
|
|
this->addEdge(oldEdges[index]);
|
|
}
|
|
}
|
|
|
|
void Automaton::writeDotToStream(std::ostream& outStream, std::vector<std::string> const& actionNames) const {
|
|
outStream << "\tsubgraph " << name << " {" << std::endl;
|
|
|
|
// Write all locations to the stream.
|
|
uint64_t locIndex = 0;
|
|
for (auto const& loc : locations) {
|
|
outStream << "\t" << name << "_s" << locIndex << "[ label=\"" << loc.getName() << "\"];" << std::endl;
|
|
++locIndex;
|
|
}
|
|
// Write for each edge an node to the stream;
|
|
uint64_t edgeIndex = 0;
|
|
for (auto const& edge : edges) {
|
|
outStream << "\t" << name << "_e" << edgeIndex << "[ label=\"\" , shape=circle, width=.2, style=filled, fillcolor=\"black\"];" << std::endl;
|
|
++edgeIndex;
|
|
|
|
// Silencing unused variable warning.
|
|
(void)edge;
|
|
}
|
|
|
|
// Connect edges
|
|
edgeIndex = 0;
|
|
for (auto const& edge : edges) {
|
|
outStream << "\t" << name << "_s" << edge.getSourceLocationIndex() << " -> " << name << "_e" << edgeIndex << " [label=\"" << actionNames.at(edge.getActionIndex()) << "\"];" << std::endl;
|
|
for (auto const& edgeDest : edge.getDestinations()) {
|
|
outStream << "\t" << name << "_e" << edgeIndex << " -> " << name << "_s" << edgeDest.getLocationIndex() << ";" << std::endl;
|
|
}
|
|
++edgeIndex;
|
|
}
|
|
|
|
|
|
outStream << "\t}" << std::endl;
|
|
|
|
}
|
|
}
|
|
}
|