Graph Input-Output

The standard graph IO enables one to store graphs and additional maps (i.e. functions on the nodes or edges) in a flexible and efficient way. Before you read this page you should be familiar with LEMON graphs and maps.

The general file format

The file contains sections in the following order:

Some of these sections can be omitted, but you will basicly need the nodeset section (unless your graph has no nodes at all) and the edgeset section (unless your graph has no edges at all).

The nodeset section describes the nodes of your graph: it identifies the nodes and gives the maps defined on them, if any. It starts with the following line:

@nodeset

The next line contains the names of the nodemaps, separated by whitespaces. Each following line describes a node in the graph: it contains the values of the maps in the right order. The map named "label" should contain unique values because it is regarded as a label map. These labels need not be numbers but they must identify the nodes uniquely for later reference. For example:

@nodeset
label  x-coord  y-coord  color
3   1.0      4.0      blue
5   2.3      5.7      red
12  7.8      2.3      green

The edgeset section is very similar to the nodeset section, it has the same coloumn oriented structure. It starts with the line

@edgeset

The next line contains the whitespace separated list of names of the edge maps. Each of the next lines describes one edge. The first two elements in the line are the labels of the source and target (or tail and head) nodes of the edge as they occur in the label node map of the nodeset section. You can also have an optional label map on the edges for later reference (which has to be unique in this case).

@edgeset
             label      weight   note
3   5        a          4.3      a-edge
5   12       c          2.6      c-edge
3   12       g          3.4      g-edge

The nodes section contains labeled (distinguished) nodes (i.e. nodes having a special label on them). The section starts with

@nodes

Each of the next lines contains a label for a node in the graph and then the label as described in the nodeset section.

@nodes 
source 3
target 12

The last section describes the labeled (distinguished) edges (i.e. edges having a special label on them). It starts with @edges and then each line contains the name of the edge and the label.

@edges 
observed c

The file may contain empty lines and comment lines. The comment lines start with an # character.

The attributes section can handle some information about the graph. It contains key-value pairs in each line (a key and the mapped value to key). The key should be a string without whitespaces, the value can be of various types.

@attributes
title "Four colored planar graph"
author "Balazs DEZSO"
copyright "Lemon Library"
version 12

Finally, the file should be closed with @end line.

Using graph input-output

The graph input and output is based on reading and writing commands. The user gives reading and writing commands to the reader or writer class, then he calls the run() method that executes all the given commands.

Writing a graph

The GraphWriter template class provides the graph output. To write a graph you should first give writing commands to the writer. You can declare writing command as NodeMap or EdgeMap writing and labeled Node and Edge writing.

GraphWriter<ListGraph> writer(std::cout, graph);

The writeNodeMap() function declares a NodeMap writing command in the GraphWriter. You should give a name to the map and the map object as parameters. The NodeMap writing command with name "label" should write a unique map because it will be regarded as a label map.

See also:
IdMap, DescriptorMap
IdMap<ListGraph, Node> nodeLabelMap;
writer.writeNodeMap("label", nodeLabelMap);

writer.writeNodeMap("x-coord", xCoordMap);
writer.writeNodeMap("y-coord", yCoordMap);
writer.writeNodeMap("color", colorMap);

With the writeEdgeMap() member function you can give an edge map writing command similar to the NodeMaps.

See also:
IdMap, DescriptorMap
DescriptorMap<ListGraph, Edge, ListGraph::EdgeMap<int> > edgeDescMap(graph);
writer.writeEdgeMap("descriptor", edgeDescMap);

writer.writeEdgeMap("weight", weightMap);
writer.writeEdgeMap("note", noteMap);

With writeNode() and writeEdge() functions you can designate Nodes and Edges in the graph. For example, you can write out the source and target node of a maximum flow instance.

writer.writeNode("source", sourceNode);
writer.writeNode("target", targetNode);

writer.writeEdge("observed", edge);

With writeAttribute() function you can write an attribute to the file.

writer.writeAttribute("author", "Balazs DEZSO");
writer.writeAttribute("version", 12);

After you give all write commands you must call the run() member function, which executes all the writing commands.

writer.run();

Reading a graph

The file to be read may contain several maps and labeled nodes or edges. If you read a graph you need not read all the maps and items just those that you need. The interface of the GraphReader is very similar to the GraphWriter but the reading method does not depend on the order of the given commands.

The reader object assumes that each not read value does not contain whitespaces, therefore it has some extra possibilities to control how it should skip the values when the string representation contains spaces.

GraphReader<ListGraph> reader(std::cin, graph);

The readNodeMap() function reads a map from the nodeset section. If there is a map that you do not want to read from the file and there are whitespaces in the string represenation of the values then you should call the skipNodeMap() template member function with proper parameters.

See also:
QuotedStringReader
reader.readNodeMap("x-coord", xCoordMap);
reader.readNodeMap("y-coord", yCoordMap);

reader.readNodeMap<QuotedStringReader>("label", labelMap);
reader.skipNodeMap<QuotedStringReader>("description");

reader.readNodeMap("color", colorMap);

With the readEdgeMap() member function you can give an edge map reading command similar to the NodeMaps.

reader.readEdgeMap("weight", weightMap);
reader.readEdgeMap("label", labelMap);

With readNode() and readEdge() functions you can read labeled Nodes and Edges.

reader.readNode("source", sourceNode);
reader.readNode("target", targetNode);

reader.readEdge("observed", edge);

With readAttribute() function you can read an attribute from the file.

std::string author;
writer.readAttribute("author", author);
int version;
writer.writeAttribute("version", version);

After you give all read commands you must call the run() member function, which executes all the commands.

reader.run();

Background of Reading and Writing

To read a map (on the nodes or edges) the GraphReader should know how to read a Value from the given map. By the default implementation the input operator reads a value from the stream and the type of the read value is the value type of the given map. When the reader should skip a value in the stream, because you do not want to store it in a map, the reader skips a character sequence without whitespaces.

If you want to change the functionality of the reader, you can use template parameters to specialize it. When you give a reading command for a map you can give a Reader type as template parameter. With this template parameter you can control how the Reader reads a value from the stream.

The reader has the next structure:

struct TypeReader {
  typedef TypeName Value;

  void read(std::istream& is, Value& value);
};

For example, the "strings" nodemap contains strings and you do not need the value of the string just the length. Then you can implement an own Reader struct.

struct LengthReader {
  typedef int Value;

  void read(std::istream& is, Value& value) {
    std::string tmp;
    is >> tmp;
    value = tmp.length();
  }
};
...
reader.readNodeMap<LengthReader>("strings", lengthMap);

The global functionality of the reader class can be changed by giving a special template parameter to the GraphReader class. By default, the template parameter is DefaultReaderTraits. A reader traits class should provide a nested template class Reader for each type, and a DefaultReader for skipping a value.

The specialization of writing is very similar to that of reading.

Undirected and Bipartite graphs

In a file describing an undirected graph (ugraph, for short) you find an uedgeset section instead of the edgeset section. The first line of the section describes the names of the maps on the undirected egdes and all next lines describe one undirected edge with the the incident nodes and the values of the map.

The format could store directed edge maps, if there are two maps with names being the same with a '+' and a '-' prefix then this could be read as such a map.

@uedgeset
             label      capacity        +flow   -flow
32   2       1          4.3             2.0     0.0
21   21      5          2.6             0.0     2.6
21   12      8          3.4             0.0     0.0

The edges section is changed to uedges section. This section describes labeled edges and undirected edges. The directed edge label should start with a '+' or a '-' prefix to decide the direction of the edge.

@uedges
uedge 1
+edge 5
-back 5

There are similar classes to the GraphReader and GraphWriter which handle the undirected graphs. These classes are the UGraphReader and UGraphWriter.

The readUEdgeMap() function reads an undirected map and the readUEdge() reads an undirected edge from the file,

reader.readUEdgeMap("capacity", capacityMap);
reader.readEdgeMap("flow", flowMap);
...
reader.readUEdge("u_edge", u_edge);
reader.readEdge("edge", edge);

The undirected bipartite graphs could be read with the BpUGraph class and it has specialized nodeset section, which should be start with "@bpnodeset". This section is separated to two subsections. The header line of these sections start with "&anodeset" or "&bnodeset" and after that the line contains the names of the regular and A-node or B-node maps accordingly. The lines of each section contains the mapped values. The labels of the graph should be unique overall both subsections.

@bpnodeset
&anodeset label   coords        radius
          0       (0, 0)        14.0
          1       (0, 1)        12.0
&bnodeset label   coords
          2       (1, 0)
          3       (1, 1)

The reading can be done with readANodeMap(), readBNodeMap() or readNodeMap() members.

reader.readNodeMap("coords", coords);
reader.readAnodeMap("radius", radius);

Advanced features

The graph reader and writer classes give an easy way to read and write graphs. But sometimes we want more advanced features. In this case we can use the more general lemon reader and writer interface.

The LEMON file format is a section oriented file format. It contains one or more sections, each starting with a line identifying its type (the word starting with the @ character). The content of the section this way cannot contain line with @ first character. The file may contains comment lines with # first character.

The LemonReader and LemonWriter gives a framework to read and write sections. There are various section reader and section writer classes which can be attached to a LemonReader or a LemonWriter.

There are default section readers and writers for reading and writing item sets, and labeled items in the graph. These read and write the format described above. Other type of data can be handled with own section reader and writer classes which are inherited from the LemonReader::SectionReader or the LemonWriter::SectionWriter classes.

The next example defines a special section reader which reads the @description sections into a string:

class DescriptionReader : LemonReader::SectionReader {
protected:
  virtual bool header(const std::string& line) {
    std::istringstream ls(line);
    std::string head;
    ls >> head;
    return head == "@description";
  }

  virtual void read(std::istream& is) {
    std::string line;
    while (getline(is, line)) {
      desc += line;
    }
  }
public:

  typedef LemonReader::SectionReader Parent;
  
  DescriptionReader(LemonReader& reader) : Parent(reader) {}

  const std::string& description() const {
    return description;
  }

private:
  std::string desc;
};

The other advanced stuff of the generalized file format is that multiple edgesets can be stored to the same nodeset. It can be used for example as a network traffic matrix.

In our example there is a network with symmetric links and there are assymetric traffic request on the network. This construction can be stored in an undirected graph and in a directed ListEdgeSet class. The example shows the input with the LemonReader class:

ListUGraph network;
ListUGraph::UEdgeMap<double> capacity;
ListEdgeSet<ListUGraph> traffic(network);
ListEdgeSet<ListUGraph>::EdgeMap<double> request(network);

LemonReader reader(std::cin);
NodeSetReader<ListUGraph> nodesetReader(reader, network);
UEdgeSetReader<ListUGraph> 
  uEdgesetReader(reader, network, nodesetReader);
uEdgesetReader.readEdgeMap("capacity", capacity);
EdgeSetReader<ListEdgeSet<ListUGraph> > 
  edgesetReader(reader, traffic, nodesetReader, "traffic");
edgesetReader.readEdgeMap("request", request);

reader.run();

Because both the GraphReader and the UGraphReader can be converted to LemonReader and it can resolve the label's of the items, the previous result can be achived with the UGraphReader class, too.

ListUGraph network;
ListUGraph::UEdgeSet<double> capacity;
ListEdgeSet<ListUGraph> traffic(network);
ListEdgeSet<ListUGraph>::EdgeMap<double> request(network);

UGraphReader<ListUGraph> reader(std::cin, network);
reader.readEdgeMap("capacity", capacity);
EdgeSetReader<ListEdgeSet<ListUGraph> > 
  edgesetReader(reader, traffic, reader, "traffic");
edgesetReader.readEdgeMap("request", request);

reader.run();

Author:
Balazs Dezso

Generated on Thu Jun 4 04:03:12 2009 for LEMON by  doxygen 1.5.9