No BSD License  

Highlights from
MatlabBGL

MatlabBGL

by

 

30 Apr 2006 (Updated )

MatlabBGL provides robust and efficient graph algorithms for Matlab using native data structures.

reweighted_graphs.m
%% Reweighted graphs in MatlabBGL
% Matlab sparse matrices only support non-zero values. Because the
% structure of the sparse matrix is used to infer the edges of an
% underlying graph this limitation prevents MatlabBGL from trivially 
% addressing graphs with 0-weight edges.
%
% To fix this problem, codes that accept a weighted graph allow the user to
% specify a vector of edge weights for each edge in the graph using the
% optional 'weights' parameter.  Using the 'weights' parameter correctly
% can be difficult due to issues with how edges are indexed in MatlabBGL.

%% Disclaimer
% The details of this section are complicated.  This means their
% implementation is error-prone.  If you get strange behavior, please let
% me know.

%% "I just want the simpliest solution..."
% *new in version 4.0*
% In this section, we'll see the really-easy but somewhat expensive way of
% reweighting a graph.  I'll run through all the cases detailed below with
% the simple code.  If you just need something to work and don't
% necessarily need to know about all the details, this section is for you!

%%
% Let's compute shortest paths in a cycle graph with only one weighted
% edge.  The simple case requires a structural and weight matrix.

%%
% n will be the total size of the graph, and u and v will be the special
% vertices connected with a weight one edge.

n = 8; % it's just an example, so let's make it small.
u = 1;
v = 2;

%%
% These commands create an undirected cycle graph.  The cycle is 
% ... n <-> 1 <-> 2 <-> ... <-> n-1 <-> n <-> 1 ...
% where the weight on every edge is 0 except for the edge between vertex
% u,v.  Notice that the edge list is already symmetric.  
%
% This setup means that while there is a weight 1 edge between u and v, the
% shortest path, or smallest weight path, is actually the path from u,
% circling every vertex except v and so d(v) should be 0.

E = [1:n 2:n 1; 2:n 1 1:n]';
w = [1 zeros(1,n-1) 1 zeros(1,n-1)]';

A = sparse(E(:,1), E(:,2), w, n, n); % create a weighted sparse matrix
As = sparse(E(:,1), E(:,2), true, n, n); % create a structural sparse matrix

%% 
% The relationship between As and A is that As should have a non-zero value
% for every edge, but the _values_ of As will be ignored and the
% computation will proceed with the _values_ in the corresponding spots in
% the matrix A.

%%
% The wrong way to compute shortest paths.

[d pred] = shortest_paths(A,u);
d(v)

%% 
% The right way to compute shortest paths.

[d pred] = shortest_paths(As,u,struct('edge_weight',edge_weight_vector(As,A)));
d(v)

%%
% That's better, d(v) = 0 as expected.


%% A first attempt
% * Correct for version 3.0 *
% A trivial example graph to illustrate the problem that occurs with 0
% weighted graphs occurs even with a simple cycle.  Suppose that the graph
% corresponding to adjacency matrix A is a symmetric cycle where all edges
% have weight 0 except for one edge between vertices (1,2).

%%
% These commands create an undirected cycle graph.  The cycle is 
% ... n <-> 1 <-> 2 <-> ... <-> n-1 <-> n <-> 1 ...
% where the weight on every edge is 0 except for the edge between vertex
% u,v.  Notice that the edge list is already symmetric.  

E = [1:n 2:n 1; 2:n 1 1:n]';
w = [1 zeros(1,n-1) 1 zeros(1,n-1)]';

A = sparse(E(:,1), E(:,2), w, n, n);

%%
% The shortest weighted path between u and v is then through the vertex 
% n because traversing the cycle in the other direction will use the 
% u,v edge of weight 1.  Let's check this with the shortest_paths function.

[d pred] = shortest_paths(A,u);
d(v)

%%
% That is weird, there is a u-v path of length 0 in the graph!  Let's see
% what path the shortest path algorithm chose.

path_from_pred(pred,v)

%%
% The path it chose was from u to v directly, taking the weight 1 edge.
% Let's look at the sparse matrix.

A

%%
% There are only two edges in the matrix corresponding to our symmetric
% weight 1 edge between u and v.  This happens because Matlab removes all 0
% weight edges from the graph.  

%% A first solution
% The solution to the problem is to use the 'edge_weight' optional
% parameter to the shortest_paths function to give it a set of weights 
% to use for each edge.  

help shortest_paths

%% 
% Well, shortest_paths says to read this document, so you are on the right
% track!  It also has a pointer to the function edge_weight_index.  Let's
% look at that function

help edge_weight_index

%%
% This function claims to help us.  It requires building a structural
% matrix which has a non-zero for each edge in the graph.  Let's do that.

As = sparse(E(:,1), E(:,2), 1, n, n)

%%
% Now the matrix has all of the required edges.  According to the
% edge_weight_index function, it returns both a matrix and an index vector.
% The index vector is a way to permute an intelligently ordered set of edge
% weights to the order that MatlabBGL requires the edge weights.  

[ei Ei] = edge_weight_index(As);

full(Ei)
ei

%%
% Now let's create a new edge weight vector for this graph corresponding to
% all the edges we want.  Each non-zero in the matrix should have an
% associated edge weight.  Most the edge weights in this case are 0, so it
% makes it simple.  

ew = zeros(nnz(As),1);
ew(Ei(u,v)) = 1;
ew(Ei(v,u)) = 1;

[d pred] = shortest_paths(As,u,struct('edge_weight', ew(ei)));

path_from_pred(pred,v)

%%
% Excellent, now the shorest path _avoids_ the edge (u,v) as we would
% expect it.  

%% A simplified solution
% The current example is somewhat tedious because we have to create the
% sparse matrix, then create the edge index matrix, and finally create and  
% edit the edge weight array.  
%
% The indexed_sparse function makes the process easier.  
% 
% Recall that using the sparse function directly generated an incorrect
% graph adjacency matrix.

A = sparse(E(:,1), E(:,2), w, n, n)

%%
% The indexed_sparse function is designed as a replacement for sparse where
% the adjacency matrix must be indexed using the edge_weight_index or
% contains 0 weight edges.

help indexed_sparse

%%
% From the documentation of indexed_sparse, the first two return values are
% the structural sparse matrix (As) and the sparse matrix (A) that sparse
% would have returned.  The final two return values are the edge index list
% that edge_weight_index returns as well as the edge index matrix.

% save the old Ei as an example
old_Ei = Ei;

[As A eil Ei] = indexed_sparse(E(:,1), E(:,2), w, n, n);

fprintf('old_Ei = \n\n');
disp(full(old_Ei));
fprintf('Ei = \n\n');
disp(full(Ei))

%% 
% Note that the edge indices changed between the two calls.  The reason for
% this change is that indexed_sparse generates edge indices based on order
% of E(:,1) and E(:,2).  Consequently, this function is much easier to use
% when you already have a set of weighted edges.
%
% In this case, we don't have to create the ew array again!  (Note that the
% call uses eil instead of ei.)

[d pred] = shortest_paths(As,u,struct('edge_weight', w(eil)));

path_from_pred(pred,v)

%% An undirected solution
% The situation for undirected graphs is more complicated.  The trouble
% with the previous solution is that each directed edge had its own weight
% in the vector w.  For an undirected graph, we really want each undirected
% edge to have a single weight, so the natural length of v would be
% nnz(A)/2 instead of nnz(A).
%
% However, MatlabBGL really treats all problems as directed graphs, so it
% will need a vector w of length nnz(A), but that vector should satisfy the
% requirement w(ei1) = w(ei2) if the edges corresponding to ei1 and ei2 are
% (i,j) and (j,i), respectively.  
%
% Again, the edge_weight_index function provides a solution to this
% problem.  We just have to tell edge_weight_index we have an undirected
% graph.

%%
% Let's start with the same sparse matrix

As = sparse(E(:,1), E(:,2), 1, n, n)

%%
% Here we use the edge_weight_index 

[ei Ei] = edge_weight_index(As,struct('undirected',1));
full(Ei) % look at the matrix

%%
% Let's create the edge weight vector
ew = zeros(nnz(As)/2,1); % only half as many zeros here.
ew(Ei(u,v)) = 1; % and we only needed to set one entry to 0

[d pred] = shortest_paths(As,u,struct('edge_weight', ew(ei)));

path_from_pred(pred,v)

%%
% And we get the same output as before!

%% The undirected simplification
% You can probably guess that the simplification for undirected graphs will
% use the indexed_sparse call again too.  

help indexed_sparse

%% 
% From the documentation, we find that indexed_sparse has an option called
% "undirected" which is set to 0 by default.  

[As A eil Ei] = indexed_sparse(E(:,1), E(:,2), w, n, n, struct('undirected',1));

fprintf('Ei = \n\n');
disp(full(Ei))

%%
% In this case, the indexed_sparse routine only issued edge indices that
% were between 1 and 8, rather than 1 and 16 as in the previous case.  

[d pred] = shortest_paths(As,u,struct('edge_weight', w(eil)));

path_from_pred(pred,v)

%% Summary
% The functions that support reweighted edges as of MatlabBGL 3.0 are 
% shortest_paths, all_shortest_paths, dijkstra_sp, bellman_ford_sp, dag_sp,
% betweenness_centrality, astar_search, johnson_all_sp,
% floyd_warshall_all_sp, mst, kruskal_mst, and prim_mst.  Note that
% max_flow does not support these indices.

%%
% The functions that assist working with the edge indices for the
% edge_weight vector are edge_weight_index, indexed_sparse, and
% edge_weight_vector.

Contact us