Matlab BGL v2.1 PDF
Matlab BGL v2.1 PDF
1
David Gleich
April 5, 2007
>> T = kruskal_mst(A);
>> T = prim_mst(A);
>> cc = components(A)
>> [a C] = biconnected_components(A);
1 http://www.boost.org/doc/graph
1
Contents
1 Installation 3
2 Motivation and Implementation 5
2.1 Graphs in Matlab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Implementation details . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Examples 8
3.1 Breadth rst search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Depth rst search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.3 Max-
ox min-cut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.4 New algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4 In-place Modication/Pass by Reference Library 15
5 Visitors 17
5.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.2 Specics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.3 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6 Features not implemented 29
7 Reference 30
7.1 Sample Graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
7.2 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2
1 Installation
We are distributing the library as a set of precompiled mex les for Windows and Linux along
with the source code for the libraries. This combination will work for all people, although it
may take a bit of eort.
If all goes well, installing the library is as easy as:
1. Unzip the le matlab_bgl.zip. For the sake of example, let's assume you unzipped it
into the same folder as I do: \/home/dgleich/matlab/" on Linux and \C:\Documents
and Settings\dgleich\My Documents\matlab\" on Windows.
2. In Matlab, add either the Linux path \/home/dgleich/matlab/matlab bgl/" or the Win-
dows path \C:\Documents and Settings\dgleich\My Documents\matlab\" to the path
(but replacing those directories with the ones where you actually unzipped matlab_
bgl.zip).
In general, the precompiled versions should work. If they do not and you would like to try
compiling the mex les from source, this section explains the process. On Windows, you
must use a Microsoft Visual Studio compiler. The free Visual Studio 2003 Compiler Toolkit2
suces for this purpose. On Linux, any recent version of gcc should work. All the precompiled
les are compiled with gcc-3.4 under Linux and the Microsoft Visual Studio 2003 compiler
on 32-bit Windows and Microsoft Visual Studio 2005 x64 cross compiler for 64-bit Windows.
To compile the .lib or .a les, rst determine which compile script you should use.
2 http://msdn.microsoft.com/visualc/vctoolkit2003/
3
System Matlab Script
32-bit Windows All compile-win32.bat
64-bit Windows Matlab 7.3 - Current compile-win64.bat
32-bit Linux Matlab 7.0 - Current compile-linux-32.sh
64-bit Linux Matlab 7.0 - Matlab 7.2 compile-linux-64.sh
64-bit Linux Matlab 7.3 - Current compile-linux-64-large.sh
Mac OS X (PPC) Matlab 7.0 - Current compile-macosx-ppc-32.sh
Mac OS X (Intel) Matlab 7.4 - Current compile-macosx-intel-32.sh
Next, you need to download and unzip version 1.33.1 of the Boost Graph Library. Goto
http://www.boost.org to download this code. Update the script le for your platform with
the correct path so that the BOOSTDIR variable gives the correct location. Check all the paths
to the various tools on Windows to make sure they correspond to your installation. Finally,
run the script le from the libmbgl directory. For example, on 32-bit Windows
E:\dev\matlab\download\matlab_bgl-2.1\libmbgl>compile-win32.bat
Setting environment for using Microsoft Visual Studio 2005 x86 tools.
Setting environment for using Microsoft Visual C++ 2003 Toolkit.
(If you have another version of Visual Studio or Visual C++ installed and wish
to use its tools from the command line, run vcvars32.bat for that version.)
Thank you for choosing the Visual C++ Toolkit 2003! Get started quickly by
building the code samples included in the "Samples" directory. Each sample
includes a short whitepaper discussing the Visual C++ features, and a batch
file for building the code.
...
On 64-bit Linux,
dgleich@icme-112-dgleich:matlab_bgl/libmbgl$ chmod u+x compile-linux-64-large.sh
dgleich@icme-112-dgleich:matlab_bgl/libmbgl$ ./compile-linux-64-large.sh
4
>> cd /home/dgleich/matlab/ % use your directory here instead of mine!
>> cd matlab_bgl/
>> cd private
>> compile
>> cd ..
>> cd @ipdouble
>> mex subsasgn.c
>> cd ..
>> cd @ipint32
>> mex subsasgn.c
>> cd ..
If you cannot compile the library and the example does not work, please send email to
[email protected] with as much output as you can.
Try running mex-setup and selecting the gcc or Microsoft Visual Studio compiler setup
for Linux and Windows, respectively.
For Linux, edit your mexopts.sh le and remove the -ansi
ag from the CFLAGS and
CXXFLAGS for your platform.
For some versions of Matlab, you may want to change the CC and CXX to gcc-3.4 or
gcc-4.0.
5
Figure 1: A directed graph.
1 0 0 0 0 0
and we labeled vertex a = 1, b = 2, v = 3, x = 4, y = 5, z = 6. In the original graph
from Figure 1, there are two edges from b to y . We have replaced both edges with a two in
the adjacency matrix. While this works for many algorithms, there are currently no ways of
implemented true multi-graphs in MatlabBGL.
We can construct this graph as a Matlab sparse matrix with the following set of commands.
>> A = sparse(6,6);
>> A(1,6) = 1;
>> A(6,1) = 1;
>> A(2,4) = 1;
>> A(2,5) = 2;
>> A(4,4) = 1;
>> A(4,6) = 1;
>> A(5,3) = 1;
>> labels = {'a';'b';'v';'x';'y';'z'};
Now, we can use the directed graph as a Matlab sparse matrix and as a MatlabBGL graph.
As we will see, we can treat any square sparse matrix as a MatlabBGL graphs.
6
Figure 2: An undirected graph.
MatlabBGL requires that undirected graphs have symmetric adjacency. When constructing
a graph, this means that you must specify each edge twice. The following Matlab session
constructs the graph in Figure 2.
>> A = sparse(6,6);
>> A(1,6) = 1;
>> A(6,1) = 1;
>> A(2,4) = 1;
>> A(4,2) = 1;
>> A(2,5) = 1;
>> A(5,2) = 1;
>> A(3,4) = 1;
>> A(4,3) = 1;
>> A(3,5) = 1;
>> A(5,3) = 1;
>> labels = {'a';'b';'v';'x';'y';'z'};
An easier way of constructed the graph from Figure 2 is to take advantage of some of
Matlab's sparse matrix routines. We can use one command to add a reverse edge for each
edge listed in a sparse matrix by executing
>> A = max(A,A');
Using this command, we can build the undirected graph using the commands:
>> A = sparse(6,6);
>> A(1,6) = 1;
>> A(2,4) = 1;
>> A(5,2) = 1;
>> A(4,3) = 1;
>> A(3,5) = 1;
>> A = max(A,A');
7
In general, any square sparse matrix in Matlab is a MatlabBGL graph; the non-zeros of the
matrix dene the edges. If the sparse matrix is symmetric, then the graph is undirected.
Currently, the max_flow function performs additional input manipulation and does not have
this optimization.
3 Examples
We'll show four examples of how to use the Matlab BGL library. The rst three examples
come from the Boost Graph Library samples. The last example shows how to write a new
algorithm using MatlabBGL.
8
3.1 Breadth rst search
In the following example, we perform a breadth rst search on an example graph from Boost.
This example is implemented in the examples/bfs example.m le.
Figure 3: The breadth rst search example graph from the Boost Graph Library. The
concentric regions show the order in which breadth rst search will visit the vertices.
We will load the graph from Figure 3 and compute the breadth rst search (BFS).
>> load graphs\bfs_example.mat
>> [d dt pred] = bfs(A,2);
>> [ignore order] = sort(dt);
>> labels(order)
ans =
's'
'r'
'w'
'v'
't'
'x'
'u'
'y'
The rst command loads the graph from the stored representation in Matlab. As we've seen,
we present the graph as a sparse adjacency matrix. We can look at the full adjacency matrix
using the full command.
>> full(A)
ans =
0 1 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0
1 0 1 1 0 1 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 1 1
0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0 0
9
The second command runs the MatlabBGL bfs command starting from vertex 2. Looking at
the label le, we can see that vertex 2 was really s in the original graph. The bfs command
returns three vectors: d is a vector of the distance to each other vertex from s ; dt is the
discover time of each vertex, the time when the BFS rst reached that vertex; and pred is
the predecessor array encoded as a Matlab tree. In fact, we can view the predecessor array
using the treeplot command.
>> treeplot(pred)
The third line of the example sorts the vertices by their discover time and saves the permu-
tation of the indices. The permutation tells us how to permute the labels array to view the
vertex labels in the discover order. The nal line actually prints the labels in their discover
order. Comparing with the original gure, we can see that the vertices were discovered in the
correct BFS order.
10
Figure 4: The depth rst search example graph from the Boost Graph Library.
'f'
'd'
'g'
'h'
'i'
The dfs command is similar to the bfs command. However, the dfs routine provides the ft
vector which indicates the nish time for each vertex as well. The other commands in this
script are explained in the rst example.
The graph in Figure 4 is disconnected. We can use the dfs command to nd all the vertices
connected to a source vertex.
>> load graphs/dfs_example.mat
>> d = dfs(A,1);
>> labels(d < 0)
ans =
'g'
'h'
'i'
This result indicates that nodes g , h, and i are in a separate component from vertex a.
11
Figure 5: The max-
ow min-cut example graph from the Boost Graph Library.
4
>> [flow cut R F] = max_flow(A,1,8);
>> full(R)
ans =
0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0
0 0 0 3 0 0 0 0
0 5 0 0 0 0 0 0
0 0 0 0 0 2 0 0
0 0 0 0 0 0 5 1
0 0 0 0 4 0 0 0
0 0 0 0 0 0 0 0
The script presents two results from the max_flow routine. The rst call computes the
maximum
ow from A to H . The second call prints the residual
ow graph R. In comparison
with Figure 5, the residual shows the unused capacity on each edge. On the edge from A to
B , there is only one unit of unused
ow, so R (1; 2) = 1.
Multiway Cut The Matlab code for this example is in the le examples/multiway example.m.
Given an undirected graph G = (V; E ) with weighted edges w . The multiway cut problem is
to nd a minimum cost set of edges, C , to remove that will disconnect a subset of vertices
S from each other. That is, after we remove the edges C from G , there is no path from any
12
max-
ow subproblems.4 Label the vertices in S , s1 ; s2 ; : : : ; s , so k = jS j. In each max-
ow
subproblem, we pick vertex s 2 S and add a new vertex t . For each s ; j 6= i , we add a
k
i j
directed edge of innite capacity from s to t . We solve the max-
ow problem and add the
j
if (~isequal(A,A'))
error('approx_multiway_cut:invalidParameter',...
'the matrix must be symmetric.');
end;
if (min(min(A)) < 0)
error('approx_multiway_cut:invalidParameter',...
'the matrix cannot contain negative weights.');
end;
n = size(A,1);
for (kk=1:length(vs))
v = vs(kk);
others = setdiff(vs,v);
13
Aflow(others,n+1) = int_infinity*ones(length(others),1);
Aflow(n+1,:) = sparse(n+1,1);
% construct a value over the edges that is 0 except on the cut, we know
% all values are positive, so just take the absolute value
vc = abs(v.*(ci(i)-ci(j)))./2;
% add the set of edges to the cut by constructing a sparse matrix with
% only the cut edges.
C = C+ sparse(i, j, vc, n,n);
end;
We can use this new algorithm to nd a set of roads to remove to disconnect a set of
nodes. The following example loads the road network for Minnesota and chooses a set of
125 vertices, calls the approx_multiway_cut command above, and then draws the cut using
the gplot command.
load graphs/minnesota.mat
n = size(A,1);
k1 = 75;
k2 = 50;
start1 = 1;
start2 = 800;
vs1 = start1:start1+k1;
vs2 = start2:start2+k2;
vs = [vs1 vs2];
C = approx_multiway_cut(A,vs);
gplot(triu(A),xy,':');
hold on;
gplot(triu(C),xy,'r-');
plot(xy(vs,1),xy(vs,2),'.');
hold off;
set(gca,'XTick',[]);
set(gca,'YTick',[]);
When drawing the graphs, we use the triu command to only select the upper triangular
portion of the adjacency matrix. Otherwise, Matlab will draw both edges, instead of only one
edge. The gure produced by this program follows.
14
4 In-place Modication/Pass by Reference Library
To facilitate the visitors implemented in the next section, MatlabBGL includes a pass-by-
reference library. This section describes that library and why it is required for MatlabBGL. In
Matlab, the standard variable passing convention is pass-by-value. That is, each time Matlab
calls a function, it copies all of the function arguments so the caller and the callee have
separate copies.5 One serious side eect is that if a function makes a single change to a
large matrix, Matlab must copy the entire matrix. Further, if we wish to return the change
to the caller, Matlab must copy the changed matrix back! For a large matrix, this can result
in serious overhead as in the following example.
For expository purposes, suppose we have a function that simply increments the rst entry
in a Matlab matrix.
function a=incr_first(a)
% INCR_FIRST Increment the first entry in a matrix.
a(1) = a(1) + 1;
Now, we go a little wild and use the code in the following script inplace example1.m.
n = 1000000;
ntrials = 1000;
a = ones(n,1);
tic;
for ii=1:ntrials
a = incr_first(a);
end
fprintf('Standard Matlab: %f seconds\n', toc);
5 Infact, Matlab optimizes this procedure and only makes a copy if the callee changes the argument. This
technique is often called \copy-on-write."
15
The script produces the following output.
>> inplace_example1
Standard Matlab: 12.375000 seconds
Twelve seconds? To simply increment the rst entry in an array 1000 times? This result is
slightly inecient.
The Inplace/pass-by-reference library remedies this ineciency. The Inplace library allows us
to makes changes to function arguments in-place. That is, the function arguments are not
copied and the caller and callee share the same variable and memory. This style of argument
passing is often called \pass-by-reference."
We designed the Inplace library to be as close to Matlab syntax as possible. To \x" the
example, we need only make a tiny change.
a = ipdouble(ones(n,1));
After this change (and changing the output label), the script produces the output.
>> inplace_example1
Inplace Calls: 0.062000 seconds
Much better!
To be slightly more concrete, the Inplace library provides two classes ipdouble and ipint32
to create pass-by-reference versions of a double and int32 matrices and vectors. To sum-
marize, the following commands all work for ipdouble and ipint32 vectors as expected.
>> ipd = ipdouble(rand(5));
>> ipd % display works
>> size(ipd) % size works
>> ipd(1,:) % subscripting works
>> ipd(1,3:end) % subscripting with end works
>> ipd(:) = rand(5) % assignment works
>> ipd(1,3:end) = ones(1,3) % partial assignment works
>> ipd(:,1) = pi*ones(5,1) % partial assignment works
The following commands do not work quite as you would expect; however there are alterna-
tives available.
>> ipd = ipdouble(ones(5)) % create a 5x5 ipdouble of ones
>> y = pi*ones(5,5); % create another vector
>> ipd2 = ipdouble(ipd); % deep copy ipd
>> ipd2 = y; % error!
Unfortunately, the Inplace library does not overload the \=" operator at the moment, so the
nal statement does not do quite what you expect. Instead of copying the contents of y to
16
ipd2, it simply overwrites the variable ipd2 with the contents of y. Instead, you must use
the assign command.
>> assign(ipd2,y); % correct!
Also, you cannot resize Inplace objects, so the following commands do not work.
>> ipd = ipdouble(ones(5)) % create a 5x5 ipdouble of ones
>> ipd(6,6) = 1; % error!
Currently, there is no workaround for this behavior. Use ipdoubles like Fortran arrays, which
have xed size.
Finally, there are some syntactic issues when you update an entire ipdouble or ipint32
vector.
>> ipd = ipdouble(pi*ones(5)); % create a 5x5 ipdouble of pi's
>> ipd = ipd + 1; % error!
>> ipd = double(ipd) + 1; % hidden error
>> assign(ipd,ipd(:)+1); % correct!
The rst error results because the plus command is not implemented for the ipdouble
type. This omission may be xed in future releases. The second \x" succeeds but contains
a hidden error. The error is that the type of the result is a Matlab double, not an ipdouble.
Finally, using the assign command results in the correct behavior.
5 Visitors
The visitor feature of the Boost Graph Library is one of the most powerful and subtle fea-
tures. By attaching visitors to some of the algorithms, you can record or alter behavior
of the underlying algorithm. Internally, the Boost Graph Library uses visitors to imple-
ment: connected_components, strong_components, biconnected_components, prim_
minimum_spanning_tree, and dijkstra_shortest_paths. Using an appropriate visitor
and data structure, all these algorithms could be implemented natively in Matlab just like the
Boost versions.6
The MatlabBGL library implements visitors using function handles.
>> load graphs/bfs_example.mat
>> ev_func = @(u) fprintf('called examine_vertex(%s)\n', char(labels(u)));
>> bfs_visitor = struct();
>> bfs_visitor.examine_vertex = ev_func;
>> breadth_first_search(A,1,bfs_visitor);
6 Ofcourse, actually implemented them natively in Matlab would negate the performance benet of using
the Boost Graph Library.
17
called examine_vertex(r)
called examine_vertex(s)
called examine_vertex(v)
called examine_vertex(w)
called examine_vertex(t)
called examine_vertex(x)
called examine_vertex(u)
called examine_vertex(y)
In the following sections, we will describe how MatlabBGL implements visitors with a high
level overview and a few examples.
5.1 Overview
In the Boost graph library and in MatlabBGL, the following algorithms support visitors:
breadth_first_search
depth_first_search
dijkstra_shortest_paths
bellman_ford_shortest_paths
astar_search.
All visitors implemented in Boost are implemented in MatlabBGL. For the details on the
visitors, see the Boost graph library documentation. Whereas the BGL uses classes and
structures to implement visitors, MatlabBGL uses function handles and structures.
To demonstrate the relationship, we will implemente the bacon_number_recorder visitor
from the Boost graph library examples.7 The algorithm used to determine Bacon numbers
is breadth rst search. In the breadth rst search algorithm, a tree-edge indicates when the
algorithm nds a new shortest path between the start node and another (previously) unknown
node. In terms of Bacon numbers, these events indicate when we nd another actor and give
us that the Bacon number of the new actor is one greater than the Bacon number of the
previous actor.
template <typename DistanceMap>
class bacon_number_recorder : public default_bfs_visitor
{
public:
bacon_number_recorder(DistanceMap dist) : d(dist) { }
7 http://www.boost.org/libs/graph/doc/kevin_bacon.html
18
template <typename Edge, typename Graph>
void tree_edge(Edge e, const Graph& g) const
{
typename graph_traits<Graph>::vertex_descriptor
u = source(e, g), v = target(e, g);
d[v] = d[u] + 1;
}
private:
DistanceMap d;
};
// Convenience function
template <typename DistanceMap>
bacon_number_recorder<DistanceMap>
record_bacon_number(DistanceMap d)
{
return bacon_number_recorder<DistanceMap>(d);
}
To use the visitor, we need to allocate storage and call breadth rst search starting from
Kevin Bacon.
// allocate storage
std::vector<int> bacon_number(num_vertices(g));
// call bfs
Vertex src = actors["Kevin Bacon"];
bacon_number[src] = 0;
The following code implements the same visitor pattern in MatlabBGL and uses the Inplace
library described in the previous section.
function bn = bacon_numbers(A,u)
% BACON_NUMBERS Compute the Bacon numbers for a graph.
%
% bn = bacon_numbers(A,u) computes the Bacon numbers for all nodes in the
% graph assuming that Kevin Bacon is node u.
19
% setup the bacon_recorder visitor
bacon_recorder = struct();
bacon_recorder.tree_edge = @tree_edge;
% call breadth_first_search
breadth_first_search(A,u,bacon_recorder);
% the end line is required with nested functions to terminate the file
end
The code for the MatlabBGL bacon_number function is in the examples/ subdirectory. In-
stead of declaring a class with a member variable d for the bacon_number_recorder, the
MatlabBGL code uses a nested function along with an ipdouble variable from the Inplace
library to accomplish the same behavior.
5.2 Specics
The Boost graph library has two types of visitor functions vertex visitors and edge visitors.
A common vertex visitor is the examine_vertex function. For the BFSVisitor concept, the
Boost graph library denes the following prototype for that visitor.
void visitor::examine_vertex(Vertex u, Graph& g)
The corresponding MatlabBGL function takes only one argument, the vertex index.
visitor.examine_vertex = @(u) fprintf('called examine_vertex(%i)!\n', u);
In MatlabBGL, the vertex visitors follow this same pattern, the only argument is the index
of the vertex.
The second type of visitor function, the edge visitors have more dierences with Boost. The
Boost edge visitor functions provide the Edge datatype directly. Because MatlabBGL does
not dene an Edge datatype like the Boost graph library, this call makes no sense. Instead,
the MatlabBGL edge visitor functions provide the transposed edge index, the source of the
edge, and the target of the edge.
visitor.examine_edge = @(ei,u,v) ...
fprintf('called examine_edge(%i,%i,%i)!\n', ei, u, v);
First, the edge index is less useful than it may seem initially. Instead of the edge index into
the original graph, the edge index is in the transposed graph that MatlabBGL is using for the
computation. (See section ??, you get the transposed edge index unless you tell MatlabBGL
20
not to transpose the graph.)) There is more about this issue in the next paragraph. The
second and third arguments, u and v are the source and target of the edge, respectively.
These two variables are quite useful and let you know which vertices the edge touches. In
the most general terms, the edge is a directed edge from vertex u to vertex v .
In fact, there are two issues with the edge index. First, the edge index comes from the
transposed graph. The second issue is that for an undirected graph, each edge has two
indices (one for the forward edge, and one for the reverse edge). To see a full discussion of
both of these issues, see the edge index example in section 5.3.
Stopping the Algorithm At any point, if a visitor function returns a zero value, the al-
gorithm halts. Often, this behavior may be desirable. Consider the following example with
astar_search.
load graphs/bgl_cities.mat
goal = 11; % Binghamton
start = 9; % Buffalo
% Use the euclidean distance to the goal as the heuristic
h = @(u) norm(xy(u,:) - xy(goal,:));
% Setup a routine to stop when we find the goal
ev = @(u) (u ~= goal);
[d pred f] = astar_search(A, start, h, ...
struct('visitor', struct('examine_vertex', ev)));
The examine_vertex function returns 0 when the vertex is a targeted vertex. In this example,
we want to nd the shortest path between Binghamton and Bualo. Once we nd a shortest
path to Bualo, we can halt the algorithm!
In summary,
MatlabBGL visitor function have the same names as the Boost graph library visitor
functions;
vertex visitor functions provide only the index of the vertex, e.g. examine_vertex(u);
edge visitor functions provide the transposed edge_index and the two endpoints of the
edge, u and v, e.g. examine_edge(ei,u,v);
the Inplace library and nested functions are useful tools to implement visitors; and
visitor function can halt an algorithm by returning 0.
For additional discussion of some of the implementation details, see section 5.3 and 5.3.
21
5.3 Examples
In this section, we give three examples to demonstrate how to use the visitor library. First, we
have an example that outputs messages from all events to show how an algorithm works. Sec-
ond, we have an example that demonstrates how to correctly use edge-indices in the visitors.
Finally, the breadth-rst-search example reimplements the bfs function using MatlabBGL
visitors instead of Boost graph library visitors.
In this example, we will write a simple visitor that outputs an algorithm's behavior. The
algorithm we will examine is dijkstra_sp. To examine the runtime behavior we will use a
visitor which outputs a string every time a function is called.
To begin, we load a graph.
>> load graphs/clr-25-2.mat
Next, let's check the documentation to see which functions to implement for the visitor
>> help dijkstra_sp
...
visitor is a struct with the following optional fields
vis.initialize_vertex(u)
vis.discover_vertex(u)
vis.examine_vertex(u)
vis.examine_edge(ei,u,v)
vis.edge_relaxed(ei,u,v)
vis.edge_not_relaxed(ei,u,v)
vis.finish_vertex(u)
...
The help states that dijkstra_sp allows visitors functions for initialize_vertex, discover_
vertex, examine_vertex, examine_edge, edge_relaxed, edge_not_relaxed, and finish_
vertex.
Rather than implementing 7 functions ourselves, we dene two helper functions. These
helper functions return functions themselves. There is one helper that returns a vertex visitor
function and one helper than returns an edge visitor function.
>> vertex_vis_print_func = @(str) @(u) ...
fprintf('%s called on %s\n', str, char(labels{u}));
>> edge_vis_print_func = @(str) @(ei,u,v) ...
fprintf('%s called on (%s,%s)\n', str, char(labels{u}), char(labels{v}));
22
examine_vertex called on s
I hope you see how these functions are useful in saving quite a bit of typing.
We are almost done. Now, we just have to setup the visitor structure to pass to the
dijkstra_sp call.
>> vis = struct();
>> vis.initialize_vertex = vertex_vis_print_func('initialize_vertex');
>> vis.discover_vertex = vertex_vis_print_func('discover_vertex');
>> vis.examine_vertex = vertex_vis_print_func('examine_vertex');
>> vis.finish_vertex = vertex_vis_print_func('finish_vertex');
>> vis.examine_edge = edge_vis_print_func('examine_edge');
>> vis.edge_relaxed = edge_vis_print_func('edge_relaxed');
>> vis.edge_not_relaxed = edge_vis_print_func('edge_not_relaxed');
23
finish_vertex called on y
relaxed. The edges that are relaxed are the shaded edges in Figure 25-5.
Finally, finish_vertex is called on a vertex after all of its edges have been examined and
possibly relaxed.
This example is implemented in the le examples/record alg.m.
Reimplementing bfs
For this example, we will implement the bfs command using the breadth_first_search
routine along with a set of visitors. The visitors will record
At the end, we benchmark this search against the MatlabBGL bfs function to estimate the
performance impact of the Matlab visitors.
The begin the algorithm, we have to initialize a series of vectors to store the data.
ip_d = -ones(num_vertices(A),1);
ip_dt = -ones(num_vertices(A),1);
ip_pred = zeros(1,num_vertices(A));
ip_time = ipdouble(1);
The bfs function requires that ip_d and ip_dt are 1 if a vertex is not reachable from the
starting vertex, so we initialize those arrays with 1. The ip_pred array is 0 if a vertex is
not in the BFS tree. The ip_time variable is used to keep a running index of how many
steps the algorithm takes.
24
With our arrays constructed, we can build visitor functions to update each of the arrays. We
won't go through the details of the following visitors, but they implement the time_stamper,
distance_recorder, and predecessor_recorder visitors from the BGL. These functions
are nested functions, so they refer to variables in the outer variable scope.
function time_discover_vertex(u)
ip_dt(u) = ip_time(1);
ip_time(1) = ip_time(1) + 1;
end
function distance_tree_edge(ei,u,v)
ip_d(v) = ip_d(u)+1;
end
function pred_tree_edge(ei,u,v)
ip_pred(v) = u;
end
With the visitor function, we simply construct the visitors by creating a set of structures and
using the combine_visitors function.
vis_distance = struct('tree_edge', @distance_tree_edge);
vis_time = struct('discover_vertex', @time_discover_vertex);
vis_pred = struct('tree_edge', @pred_tree_edge);
The only remaining task is to call the breadth_first_search algorithm. Prior to making
the call, we must nish some brief initialization to properly denote the source vertex.
ip_d(u) = 0;
ip_dt(u) = ip_time(1);
breadth_first_search(A,u,vis);
After the call to breadth_first_search, the desired data remains in the ip_d, ip_dt, and
ip_pred variables. To nish, we convert these to real double times from the ipdouble
types used in the algorithms.
The bfs_in_mbgl function is provided in the examples/ directory. We can use this im-
plementation to benchmark the performance dierence between the Matlab based visitor
implementation and the native visitor implementation.
% from the examples subdirectory
>> load ../graphs/minnesota.mat
>> tic;
>> [d dt pred] = bfs(A,1);
>> toc;
>> tic;
25
>> [d dt pred] = bfs_in_mbgl(A,1);
>> toc;
Elapsed time is 0.000000 seconds.
Elapsed time is 2.469000 seconds.
Unfortunately, there is signicant overhead in calling the MatlabBGL visitors, and the Mat-
labBGL implementation is much slower on even a small graph (2642 vertices). Nevertheless,
the interactive computation environment in Matlab oers signicant speed advantages and
reduced development time.
Becoming more ecient The previous example was needlessly inecient. Here, we explain
how to implement the previous example in a faster and more ecient manner. First, the
combine_visitors function is a nice general function, but there is overhead involved in each
call to the visitor. Also, there is an alternative to using the Inplace library for the visitors
which yields a slight performance increase.
Brie
y, we dene the arrays without the Inplace library.
ip_d = -ones(num_vertices(A),1);
ip_dt = -ones(num_vertices(A),1);
ip_pred = zeros(1,num_vertices(A));
ip_time = 1;
Then, we dene a single pair of nested functions which perform all the visitor tasks.
function discover_vertex(u)
ip_dt(u) = ip_time(1);
ip_time(1) = ip_time(1) + 1;
end
function tree_edge(ei,u,v)
ip_d(v) = ip_d(u)+1;
ip_pred(v) = u;
end
Finally, we construct the visitor structure, setup the initial values for each variable, and call
breadth_first_search.
vis = struct('discover_vertex', @discover_vertex, 'tree_edge', @tree_edge);
ip_d(u) = 0;
ip_dt(u) = ip_time(1);
breadth_first_search(A,u,vis);
26
The code for the ecient version is in the bfs_in_mbgl_efficient function is provided in
the examples/ directory. Using this version, we recompute the timings.
% from the examples subdirectory
>> load ../graphs/minnesota.mat
>> tic;
>> [d dt pred] = bfs(A,1);
>> toc;
>> tic;
>> [d dt pred] = bfs_in_mbgl_efficient(A,1);
>> toc;
Elapsed time is 0.000000 seconds.
Elapsed time is 0.469000 seconds.
These small changes have made a considerable change in the runtime of the function. While
the native code is still considerably faster, the new code is an improvement.
To reiterate, the code for this example is implemented in the bfs_in_mbgl and bfs_in_
mbgl_efficient functions in the examples/ directory.
This example is, perhaps, the most intricate. Here, we will delve into how to use the edge
index provided in the edge visitor functions.
To begin, let's determine the problem. Suppose we have a graph with a numeric value
associated with each edge. One example would be a weighted graph, however, this example
is intended to be fairly general. Eectively, we will describe how to work with edge property
maps and MatlabBGL. As always, we need a graph to use.
>> load graphs/bfs_example.mat
Presently, we have an implicit association between edges and values. That is, each directed
edge in A has a separate value. In the next statement, we make the mapping concrete and
explicitly indicate which value we want for each edge.8
>> Av = sparse(i, j, edge_rand, size(A,1), size(A,2));
8 Thisstep is not required or recommended. We perform this step to be completely concrete about the
intended mapping. In your own code, you should always maintain the mapping implicitly for the highest perfor-
mance.
27
The rst case we demonstrate is the \obvious" usage, but contains an error. We dene an
edge visitor to write out some data on every edge the algorithm examines.
>> ee = @(ei,u,v) fprintf(...
'examine_edge %2i, %1i, %1i, %4f, %4f\n', ...
ei, u, v, edge_rand(trans_ei_to_ei(ei)), Av(u,v));
Also, let's write a quick heading so that we can read and understand the the output before
we call the breadth_first_search algorithm.
>> fprintf(' ei, u, v, er(ei),true er(ei)\n');
>> breadth_first_search(A,1,struct('examine_edge',ee));
ei, u, v, er(ei), A(u,v)
examine_edge 1, 1, 2, 0.950129, 0.606843
examine_edge 2, 1, 5, 0.231139, 0.444703
examine_edge 3, 2, 1, 0.606843, 0.950129
examine_edge 4, 2, 6, 0.485982, 0.615432
examine_edge 10, 5, 1, 0.444703, 0.231139
...
Quickly, we can see that using the edge index itself does not give us the correct mapping
between edges and edge-values. Recall that A(u,v) was the intended value, but all the edge
values are stored with an implicit order in edge_rand.
Note: The edge index cannot be trivially used to index edge values.
In order to make the edge index return the correct value, we must dene an edge index map.
In Matlab, this means we need to create a vector with an entry for each edge in the graph such
that the entry indexed by the transposed edge index returns the actual edge index. Eectively,
we want to undo the transposition that occurs when we use the MatlabBGL library.9 We can
eciently construct the map using the following two commands.
>> [i,j,val] = find(A);
>> Aind = sparse(i,j,1:num_edges(A),size(A,1), size(A,2));
>> [i,j,trans_ei_to_ei] = find(Aind');
These lines create a new sparse matrix with the same sparsity pattern as A, but where each
value is replaced by its edge index. To create the edge index map, we simply read out the
transposed matrix into a value array.
With the edge index map in hand, we can correctly code the previous example.
>> ee = @(ei,u,v) fprintf('examine_edge %2i, %1i, %1i, %4f, %4f\n', ...
ei, u, v, edge_rand(trans_ei_to_ei(ei)), Av(u,v));
>> fprintf(' ei, u, v, er(ei),true er(ei)\n');
>> breadth_first_search(A,1,struct('examine_edge',ee));
9 If you use the notrans option, then this section may or may not apply to you.
28
ei, u, v, er(ei),true er(ei)
examine_edge 1, 1, 2, 0.202647, 0.202647
examine_edge 2, 1, 5, 0.502813, 0.502813
examine_edge 3, 2, 1, 0.846221, 0.846221
examine_edge 4, 2, 6, 0.709471, 0.709471
examine_edge 10, 5, 1, 0.525152, 0.525152
...
The code now correctly indexes the edge_rand array and gets the correct value for each
edge.
The rst two examples assumed that each edge (u; v ) and (v ; u ) have distinct values. Instead,
suppose we have a single value for both (u; v ) and (v ; u ) and only store half of them. We
need to build a trans_ei_to_ei map that correct handles this case as well. The only change
is that we only deal with the upper triangular part of the matrix instead of the full matrix.
The following code computes an appropriate trans_ei_to_ei to index a new edge_rand
vector that is dened for each undirected edge.
>> [i,j,val] = find(triu(A,1));
>> edge_rand = rand(num_edges(A)/2,1);
>> % build a tranposed edge index to edge index map
>> Aind = sparse([i; j],[j; i],[1:num_edges(A)/2 1:num_edges(A)/2], size(A,1), size(A,2));
>> [i,j,trans_ei_to_ei] = find(Aind');
>> ee = @(ei,u,v) fprintf('examine_edge %2i, %1i, %1i, %4f, %4f\n', ...
>> ei, u, v, edge_rand(trans_ei_to_ei(ei)), edge_rand(Aind(u,v)));
>> fprintf(' ei, u, v, er(ei),true er(ei)\n');
>> breadth_first_search(A,1,struct('examine_edge',ee));
ei, u, v, er(ei),true er(ei)
examine_edge 1, 1, 2, 0.150873, 0.150873
examine_edge 2, 1, 5, 0.378373, 0.378373
examine_edge 3, 2, 1, 0.150873, 0.150873
examine_edge 4, 2, 6, 0.860012, 0.860012
examine_edge 10, 5, 1, 0.378373, 0.378373
...
In this output, notice that (1; 2) and (2; 1) both map to the same set of values.
This example is implemented in the le examples/edge index example.m.
max- ow intermediate data: Currently, the max- ow algorithm generates a large amount
29
of intermediate data that could be cached between calls if the underlying graph does
not change.
support for more algorithms from the Boost Graph Library: I chose not to implement
more algorithms from Boost until there is demand or time allows. The matrix ordering
commands are redundant in Matlab because they are already built in.
edge labeled graph type: The support for graphs with edge labels is limited. Although
the Matlab sparse matrix type easily supports subgraphs, for a graph with edge labels,
computing a subgraph is more dicult.
better graph drawing tools: the gplotwl function in the Matlab File Exchange plots
both weights and labels
7 Reference
7.1 Sample Graphs
This section lists the set of sample graphs provided with MatlabBGL. The table should be
read as clr-26-1.mat is a directed, weighted graph without labels or node coordinates, the
graph came from CLR Figure 26-1. The source CLR is The source KT is Kleinberg and
Tardos, Algorithm Design, 2006.
30
Name Dir. Weighted Labels Coords. Source
clr-24-1 CLR10 Fig. 24.1
clr-25-2 CLR Fig. 25.2
clr-26-1 CLR Fig. 26.1
clr-27-1 CLR Fig. 27.1
kt-3-2 KT11 Fig. 3.2
kt-3-7 KT Fig. 3.7
kt-6-23 KT Fig. 6.23
kt-7-2 KT Fig. 7.2
tarjan-biconn Tarjan12 Fig. 2
padgett-
orentine Website13
minnesota Highway Data14
tapir meshpart15
cs-stanford Partial webbase 2001 crawl16
7.2 Functions
Searches
bfs
BFS Compute the breadth first search order.
[d dt pred] = bfs(A,u) returns the distance to each vertex (d) and the
discover time (dt) in a breadth first search starting from vertex u.
d(i) = dt(i) = -1 if vertex i is not reachable from vertex u.
pred is the predecessor array. pred(i) = 0 if vertex (i)
is in a component not reachable from u and i != u.
31
Note: this function does not depend upon the non-zero values of A, but
only uses the non-zero structure of A.
Example:
load graphs/bfs_example.mat
d = bfs(A,1)
32
dfs
DFS Compute the depth first search times.
[d dt ft pred] = dfs(A,u) returns the distance (d), the discover (dt) and
finish time (ft) for each vertex in the graph in a depth first search
starting from vertex u.
d = dt(i) = ft(i) = -1 if vertex i is not reachable from u
pred is the predecessor array. pred(i) = 0 if vertex (i)
is in a component not reachable from u and i != u.
Note 1: When computing the full dfs, the vertex u is ignored, vertex 1 is
always used as the starting vertex.
Note: this function does not depend upon the non-zero values of A, but
only uses the non-zero structure of A.
Example:
load graphs/dfs_example.mat
d = dfs(A,1)
33
breadth rst search
BREADTH_FIRST_SEARCH Fully wrap the Boost breadth_first_search call
including the bfs_visitor.
Note: this function does not depend upon the non-zero values of A, but
only uses the non-zero structure of A.
Example:
This example finds the distance to a single point and stops the search.
function dist_uv(A,u,v,options)
vstar = v;
dmap = ipdouble(zeros(size(A,1),1));
function stop=on_tree_edge(ei,u,v)
dmap[v] = dmap[u]+1;
return (v ~= vstar);
34
end;
breadth_first_search(A,u,struct('tree_edge',@on_tree_edge),options);
end;
35
depth rst search
DEPTH_FIRST_SEARCH Fully wrap the Boost depth_first_search call
including the dfs_visitor.
Note 1: When computing the full dfs, the vertex u is ignored, vertex 1 is
always used as the starting vertex.
Note: this function does not depend upon the non-zero values of A, but
only uses the non-zero structure of A.
Example:
This example finds the distance to a single point and stops the search.
function dist_uv(A,u,v,options)
vstar = v;
dmap = ipdouble(zeros(size(A,1),1));
36
function stop=on_tree_edge(ei,u,v)
dmap[v] = dmap[u]+1;
return (v ~= vstar);
end;
breadth_first_search(A,u,struct('tree_edge',@on_tree_edge),options);
end;
37
Components
components
Simulink components.
[ci sizes] = components(A) returns the component index vector (ci) and
the size of each of the connected components (sizes). The number of
connected components is max(components(A)). The algorithm used computes
the strongly connected components of A, which are the connected
components of A if A is undirected (i.e. symmetric).
Note: this function does not depend upon the non-zero values of A, but
only uses the non-zero structure of A.
Example:
load graphs/dfs_example.mat
components(A)
38
biconnected components
BICONNECTED_COMPONENTS Compute the biconnected components and
articulation points for a symmetric graph A.
Note: this function does not depend upon the non-zero values of A, but
only uses the non-zero structure of A.
Example:
load graphs/tarjan-biconn.mat
biconnected_components(A)
39
Shortest Paths
shortest paths
SHORTEST_PATHS Compute the weighted single source shortest path problem.
Example:
load graphs/clr-25-2.mat
shortest_paths(A,1)
shortest_paths(A,1,struct('algname','bellman_ford'))
40
all shortest paths
all_shortest_paths Compute the weighted all pairs shortest path problem.
Example:
load graphs/clr-26-1.mat
all_shortest_paths(A)
all_shortest_paths(A,struct('algname','johnson'))
41
dijkstra sp
DIJKSTRA_SP Compute the weighted single source shortest path problem.
Dijkstra's algorithm for the single source shortest path problem only
works on graphs without negative edge weights.
The options structure can contain a visitor for the Dijkstra algorithm.
Example:
load graphs/clr-25-2.mat
dijkstra_sp(A,1)
42
bellman ford sp
BELLMAN_FORD_SP Compute the weighted single source shortest path problem.
The Bellman-Ford algorithm for the single source shortest path problem
works on graphs with negative edge weights.
This method works on weighted directed graphs with negative edge weights.
The runtime is O(VE).
Example:
load graphs/kt-6-23.mat
d = bellman_ford_sp(A,1);
43
dag sp
DAG_SP Compute the weighted single source shortest path problem.
The DAG shortest path algorithm for the single source shortest path
problem only works on directed acyclic-graphs (DAGs).
If the graph is not a DAG, the results are undefined. In the future, the
function may throw an error if the graph is not a DAG.
Example:
load graphs/kt-3-7.mat
dag_sp(A,1)
44
johnson all sp
JOHNSON_ALL_SP Compute the weighted all-pairs shortest path problem.
Example:
load graphs/clr-26-1.mat
johnson_all_sp(A)
45
oyd warshall all sp
FLOYD_WARSHALL_ALL_SP Compute the weighted all-pairs shortest path problem.
Example:
load graphs/clr-26-1.mat
floyd_warshall_all_sp(A)
46
astar search
ASTAR_SEARCH Perform a heuristically guided (A*) search on the graph.
Note: You can specify a visitor for this algorithm. The visitor has the
following optional functions.
vis.initialize_vertex(u)
vis.discover_vertex(u)
vis.examine_vertex(u)
vis.examine_edge(ei,u,v)
vis.edge_relaxed(ei,u,v)
vis.edge_not_relaxed(ei,u,v)
vis.black_target(ei,u,v)
vis.finish_vertex(u)
Each visitor parameter should be a function pointer, which returns 0
if the search should stop. (If the function does not return anything,
the algorithm continues.)
Example:
load graphs/bgl_cities.mat
goal = 11; % Binghamton
start = 9; % Buffalo
% Use the euclidean distance to the goal as the heuristic
h = @(u) norm(xy(u,:) - xy(goal,:));
% Setup a routine to stop when we find the goal
ev = @(u) (u ~= goal);
[d pred f] = astar_search(A, start, h, ...
struct('visitor', struct('examine_vertex', ev)));
47
Minimum Spanning Trees
mst
MST Compute a minimum spanning tree for an undirected graph A.
Example:
load graphs/clr-24-1.mat
mst(A)
48
kruskal mst
KRUSKAL_MST Compute a minimum spanning with Kruskal's algorithm.
The Kruskal MST algorithm computes a minimum spanning tree for a graph.
See the mst function for calling information. This function just calls
mst(...,struct('algname','kruskal'));
Example:
load graphs/clr-24-1.mat
kruskal_mst(A)
49
prim mst
PRIM_MST Compute a minimum spanning with Kruskal's algorithm.
Example:
load graphs/clr-24-1.mat
prim_mst(A)
50
Statistics
num edges
NUM_EDGES The number of edges in a graph.
Example:
load graphs/dfs_example.mat
n = num_edges(A)
51
num vertices
NUM_VERTICES The number of vertices in a graph.
Example:
A = sparse(ones(5));
n = num_vertices(A);
52
clustering coecients
CLUSTERING_COEFFICIENTS Compute the clustering coefficients for vertices.
Note: this function does not depend upon the non-zero values of A, but
only uses the non-zero structure of A.
Example:
load graphs\clique-10.mat
clustering_coefficients(A)
53
betweenness centrality
BETWEENNESS_CENTRALITY Compute the betweenness centrality for vertices.
Note: the edge centrality can also be returned as an edge list using the
options.ec_list options. This option can eliminate some ambiguity in the
output matrix E when the edge centrality of an edge is 0 and Matlab drops
the edge from the sparse matrix.
Example:
load graphs/padgett-florentine.mat
betweenness_centrality(A)
54
Flow
max
ow
MAX_FLOW Compute the max flow on A from u to v.
Example:
load graphs\max_flow_example.mat
max_flow(A,1,8)
55
Graphs
erdos reyni
ERDOS_REYNI Generates a random Erdos-Reyni (Gnp) graph
This function is different from the Boost Graph library version, it was
reimplemented natively in Matlab.
Example:
A = erdos_reyni(100,0.05);
56
cycle graph
CYCLE_GRAPH Generate the cycle graph of order n
Example:
[A xy] = cycle_graph(10);
gplot(A,xy);
57
star graph
STAR_GRAPH Generate the star graph of order n
The star graph is a simple star with n vertices. Vertex n is the center
vertex.
Example:
[A xy] = star_graph(10);
gplot(A,xy);
58
wheel graph
WHEEL_GRAPH Construct a wheel graph of order n
The wheel graph is a cycle graph of order n-1 along with an additional
vertex that connects all the remaining vertices. (Run the example and it
will be extremely clear if you are still confused.)
[A xy] = wheel_graph(n) returns the adjacency matrix for the wheel graph
of order n. The matrix xy stores two-dimensional coordinates for each
vertex.
Example:
[A xy] = wheel_graph(10);
gplot(A,xy);
59
Visitors
combine visitors
COMBINE_VISITORS Generate a new visitor by combining existing visitors
The value returned by the combined visitor function is the bitwise & of
all the individual return values. So, if any visitor requests the
algorithm to halt, then the algorithm will halt.
Example:
vis1 = struct();
vis1.examine_vertex = @(u) fprintf('vis1: examine_vertex(%i)\n', u);
vis2 = struct();
vis2.examine_vertex = @(u) fprintf('vis2: examine_vertex(%i)\n', u);
combined_vis = combine_visitors(vis1, vis2);
load graphs/bfs_example.mat
breadth_first_search(A,1,combined_vis);
60
Options
old_default = set_matlab_bgl_default(options)
options.istrans: the input matrices are already transposed [{0} | 1]
options.nocheck: skip the input checking [{0} | 1]
options.full2sparse: convert full matrices to sparse [{0} | 1]
These options can make the Matlab BGL interface more efficient by
eliminating the copying operations that occur between Matlab's structures
and the BGL structures. However, they are more difficult to use and are
disabled by default.
Generally, they are best used when you want to perform a large series of
computations.
e.g.
% tranpose the matrix initially...
At = A'
old_options = set_matlab_bgl_default(struct('istrans',1));
% perform a bunch of graph work with At...
d1 = dfs(At,1); d2 = dfs(At,2); ...
% restore the old options
set_matlab_bgl_default(old_options);
61