Mercurial > hg > camir-aes2014
diff core/tools/DiGraph.m @ 0:e9a9cd732c1e tip
first hg version after svn
author | wolffd |
---|---|
date | Tue, 10 Feb 2015 15:05:51 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/tools/DiGraph.m Tue Feb 10 15:05:51 2015 +0000 @@ -0,0 +1,853 @@ + +% GENERAL GRAPH THEORY CLASS: DiGraph +% +% CAVE: Any special application oriented stuff +% should be outside here + +classdef DiGraph < handle + +properties + + V = sparse(0,1); % boolean for nodes + Vlbl = {}; + + E = sparse(0,0); % sparse weighted edge graph +end + +methods + +% --- +% Constructor +% --- +function G = DiGraph(V, E, Vlbl) + + if nargin == 1 + if ~isnumeric(V) + + G = DiGraph.copy(V); + else + % build graph from Edge Adjacency Matrix + G.V = sparse(find(sum(V + V')) > 0); + G.E = V; + end + end + + if nargin >= 2 + G = DiGraph(); + + if numel(V) ~= max(size(E)) + error 'wrong dimensions'; + end + + % --- + % We copy existent nodes and edges + % --- + if numel(V) == size(V,2) + G.V = V; + else + G.V = V'; + end + G.E = E; + + % --- + % We copy the labels for existent nodes, + % if supplied. + % NOTE: the FIND call is neccessary, as logical indexing + % does not work in this case + % TODO: WHY NOT? + % --- + if (nargin == 3) && ~isempty(Vlbl) + G.Vlbl = cell(numel(G.V),1); + G.Vlbl(find(G.V)) = Vlbl(find(G.V)); + end + + end +end + +% get list of node ids +function out = nodes(G) + out = find(G.V); +end + +% get list of node ids +function out = last_node(G) + out = find(G.V,1,'last'); +end + +function w = edge(G, V1, V2) + + w = G.E(V1, V2); +end + +% get edge list representation +function [val, V1, V2] = edges(G, V1) + + if nargin == 1 + + % get all edges of the graph + [V1, V2] = find(G.E); + + val = zeros(numel(V1), 1); + + for i = 1:numel(V1) + val(i) = G.E(V1(i), V2(i)); + end + + elseif nargin == 2 + + % get all edges coming from or entering this node + V2 = find(G.E(V1, :)); + val = G.E(V1, V2); + + V2 = [ V2 find(G.E(:,V1))']; + val = [ val G.E(V2, V1)']; + end +end + +% compare two graphs +function out = eq(a,b) + + % --- + % compare graph stats + % necessary + % --- + if a.order ~= b.order + out = false; + cprint(3, 'eq: graph node numbers do not match'); + return + end + + if a.num_edges ~= b.num_edges + out = false; + cprint(3, 'eq: graph edge numbers do not match'); + return + end + + + % --- + % compare labels and reindex graph b if + % necessary + % --- + lbla = a.label(); + lblb = b.label(); + + for i = 1:numel(lbla) + if ~isempty(lbla{i}) + tmpidx = strcellfind(lblb, lbla{i}); + + % check for substring problems + for j = 1:numel(tmpidx) + if strcmp(lblb{tmpidx(j)}, lbla{i}) + idx(i) = tmpidx(j); + break; + end + end + end + end + + % test on found labels + num_matching_lbl = sum(idx > 0); + if ~isempty(lbla) && (num_matching_lbl < a.order) + out = false; + cprint(3, 'eq: graph labels do not match'); + return + end + + % --- + % reindex edges and nodes: only replace valid indexes + % --- + val_idx = idx > 0; + idx = idx(val_idx); + + bE(val_idx,val_idx) = b.E(idx,idx); + bV(val_idx) = b.V(idx); + + if sum(a.V ~= bV) > 0 + out = false; + cprint(3, 'eq: nodes do not match'); + return + end + + if sum(sum(a.E ~= bE)) > 0 + out = false; + cprint(3, 'eq: edges do not match'); + return + end + + % --- + % OK, seems these Graphs are the same!!! + % --- + out = true; + +end + +% find node via its label +function Vid = node(G, label) + lbl = G.label(); + + Vid = strcellfind(lbl, label,1); +end + +% add a node +function add_node(G,V) + + % set node indicator + G.V(V) = 1; + + G.E(V,V) = 0; +end + +% remove a node +function remove_node(G,V) + + % remove node + G.V(V) = 0; + + % remove all edges + G.E(V,:) = 0; + G.E(:,V) = 0; +end + +% remove an edge +function remove_edge(G, V1, V2) + + G.E(V1, V2) = 0; +end + +% add an edge +function add_edge(G, V1, V2, weight) + + G.add_node(V1); + G.add_node(V2); + + if G.E(V1,V2) == 0 + + % save weight + set_edge(G, V1, V2, weight); + else + + join_edge(G, V1, V2, weight) + end +end + +% --- +% implementation of edge joining, +% to be overwritten for several +% purposes +% --- +function join_edge(G, V1, V2, weight) + + set_edge(G, V1, V2, G.edge(V1, V2) + weight); +end + +% --- +% sets the edge without considering earlier weights +% --- +function set_edge(G, V1, V2, weight) + + G.E(V1, V2) = weight; +end + +% --- +% Graph-specific functions +% --- +function c = children(G, Vid) + + c = find(G.E(Vid,:) > 0); +end + +function p = parents(G, Vid) + + p = find(G.E(:,Vid) > 0)'; +end + +% --- +% Vertex Degree +% --- +function out = degree(G, V) + + out = sum(G.E(V,:) > 0) + sum(G.E(:,V) > 0); +end + +% --- +% Vertex Degree +% --- +function out = degree_in(G, V) + + out = sum(G.E(V,:) > 0); +end + +% --- +% Max Degree In +% --- +function out = max_degree_in(G) + + out = max(sum(G.E > 0, 2)); +end + +% --- +% Vertex Degree +% --- +function out = degree_out(G, V) + + out = sum(G.E(:,V) > 0); +end + +% --- +% Max Degree In +% --- +function out = max_degree_out(G) + + out = max(sum(G.E > 0, 1)); +end + + +% --- +% Max Degree +% --- +function out = max_degree(G) + + out = max(sum(G.E > 0, 1) + sum(G.E > 0, 2)'); +end + +% --- +% Max weight +% --- +function out = max_weight(G) + + out = max(max(G.E)); +end + +% --- +% Number of Vertices in Graph +% --- +function out = order(G) + out = sum(G.V); +end + +% --- +% Number of Edges in Graph +% --- +function out = num_edges(G) + out = sum(sum(G.E ~= 0)); +end + +% --- +% Number of Vertices in Graph +% --- +function out = cardinality(G) + out = order(G); +end + +function Gs = unconnected_subgraph(G) + Vtmp = (sum(G.E + G.E') == 0); + Etmp = sparse(size(G.E, 1), size(G.E, 1)); + + Gs = DiGraph(Vtmp, Etmp, G.label()); +end + +% --- +% return string labels for a (set of) node(S) +% --- +function out = label(G, Vid) + + out = {}; + if nargin == 1 + % maybe much faster for whole graph + if (numel(G.Vlbl) < G.order() || isempty(G.Vlbl)) + + out = cell(numel(G.V), 1); + for i = 1:numel(Vid) + out{i} = sprintf('%d', Vid(i)); + end + else + out = G.Vlbl; + end + + elseif nargin == 2 + for i = 1:numel(Vid) + + if (numel(G.Vlbl) < Vid(i)) || isempty(G.Vlbl{Vid(i)}) + + if numel(Vid) > 1 + out{i} = sprintf('%d', Vid(i)); + else + out = sprintf('%d', Vid(i)); + end + else + if numel(Vid) > 1 + out{i} = G.Vlbl{Vid(i)}; + else + out = G.Vlbl{Vid(i)}; + end + end + end + end +end + + +% ----------------------------------------------------------------- +% Graph theory algorithms +% --- + + +% --- +% sets all the main diagonal edges to zero +% --- +function remove_cycles_length1(G) + + for i = G.nodes(); + G.E(i,i) = 0; + end +end + + +% --- +% Returns whether a given graph has cycles or not, +% using the SCC search +% --- +function out = isAcyclic(G) + % --- + % We get all the sccs in the DiGraph, and + % return true if none of the sccs has more than + % one node + % --- + + % check for self-loops + if sum(abs(diag(G.E))) > 0 + out = 0; + return; + end + + [~, s, ~] = strongly_connected_components(G); + + if max(s) < 2 + out = 1; + else + out = 0; + end +end + +% --- +% All SCC's ordered by number of nodes in graph +% +% this should also be able to return +% the SCC of just one node +% --- +function [Gs, s, id] = strongly_connected_components(G, Vin) + + % marking + marked = zeros(size(G.V)); + + % --- + % two stacks, + % one: has not been assigned to scc + % two: unclear if in same scc + % --- + stack1 = CStack(); + stack2 = CStack(); + + % initialise scc ids + id = zeros(size(G.V)) - 1; % assigned scc? + idctr = 1; + + % initialise graph ordering + preorder = zeros(G.order, 1); + prectr = 0; + + for v = G.nodes(); + if ~marked(v) + dfs(G, v); + end + end + + % --- + % create subgraphs (DiGraph here) for sccs + % --- + if nargin == 1 + + s = zeros(idctr-1,1); + for idctr2 = 1:idctr-1 + Vtmp = (id == idctr2); + Emask = sparse(size(G.E, 1), size(G.E, 1)); + Emask(Vtmp,Vtmp) = 1; + Etmp = G.E .* Emask; + + Gs(idctr2) = DiGraph(Vtmp, Etmp, G.Vlbl); + s(idctr2) = Gs(idctr2).order(); + end + + % --- + % order by number of nodes + % --- + [s, idx] = sort(s,'descend'); + Gs = Gs(idx); + Gmax = Gs(1); + + else + % --- + % get just the scc for the questioned node + % --- + Vtmp = (id == id(Vin)); + Emask = sparse(size(G.E, 1), size(G.E, 1)); + Emask(Vtmp,Vtmp) = 1; + Etmp = G.E .* Emask; + + Gs = DiGraph(Vtmp, Etmp); + s = Gs.order(); + end + + % --- + % NOTE: THIS IS A NESTED DFS BASED GRAPH ORDERING + % --- + function dfs(G, v) + + % mark this node as visited + marked(v) = 1; + + preorder(v) = prectr; + prectr = prectr + 1; + + % push into both stacks + stack1.push(v); + stack2.push(v); + + % --- + % dfs + % --- + for w = G.children(v) + if ~marked(w) + % --- + % traverse into dfs if not yet visited + % --- + dfs(G, w); + + elseif id(w) == -1 + + % --- + % w has not yet been assigned to a strongly connected + % component + % --- + while ((preorder(stack2.top()) > preorder(w))) + stack2.pop(); + end + end + end + + % --- + % found scc containing v + % --- + if (stack2.top() == v) + stack2.pop(); + + w = -1; + while (w ~= v) + + % --- + % collect all the nodes of this scc + % --- + w = stack1.pop(); + id(w) = idctr; + end + idctr = idctr + 1; + end + + end % function dfs +end + +function [Gs, s, id] = connected_components(G, varargin) + % --- + % get all connected subgraphs: + % --- + + % make edge matrix undirected + G2 = DiGraph(Graph(G)); + + [GsGraph, s, id] = strongly_connected_components(G2, varargin{:}); + + % get the actual subgraps + + for i =1:numel(GsGraph) + Gs(i) = G.subgraph(GsGraph(i).nodes); + end +end + +% --- +% creates new graph just containing the +% specified nodes and optionally specified edges +% nodes can be specified using +% a. the binary G.V structure +% b. or node indices +% --- +function G2 = subgraph(G, V, E) + if nargin == 2 + E = []; + end + + % --- + % create new graph as copy ofthe old + % --- + + G2 = feval(class(G), G); + + % --- + % reset nodes and edges + % NOTE: we determine the input format first + % --- + if (max(V) == 1 && numel(V) > 1) || max(V) == 0 + + G2.remove_node(find(~V)); + else + + G2.remove_node(setdiff(1:numel(G.V), V)); + end + if ~isempty(E) + G2.E = E; + end +end + +% --- +% joins the information of graph G2 with +% this GRaph, not duplicating nodes. +% --- +function add_graph(G, G2) + + % determine if symmetric edges have to be added + clas = cat(1, class(G2), superclasses(G2)); + if strcellfind(clas, 'Graph') + add_symm = 1; + else + add_symm = 0; + end + + % --- + % add all nodes and edges in G2 + % --- + for V = G2.nodes(); + + G.add_node(V); + end + + % --- + % NOTE / TODO: + % this LABEL inheritance is far too expensive when + % creating many copiesof the same graph + % Its also unnessecary for lots of them + % except for debugging :(. + % --- + G.Vlbl = cell(1,numel(G2.V)); + G.Vlbl(G2.nodes()) = G2.label(G2.nodes()); + + % --- + % get all edges in G2 + % --- + [val, V1, V2] = edges(G2); + + for i = 1:numel(val) + + % --- + % add edges to graph + % --- + G.add_edge(V1(i), V2(i), val(i)); + + if add_symm + % --- + % add symmetric edges to graph + % --- + G.add_edge(V2(i), V1(i), val(i)); + end + + end +end + +% --- +% substracts the edges in G2 from +% this GRaph, removing not connected nodes. +% --- +function Gout = minus(a, b) + Gout = feval(class(a),a); + Gout.remove_graph(b); +end + +function remove_graph(G, G2) + + % determine if symmetric edges have to be added + clas = cat(1, class(G2), superclasses(G2)); + if strcellfind(clas, 'Graph') + symm = 1; + else + symm = 0; + end + + % remove edges + [val, V1, V2] = G2.edges(); + for j = 1:numel(val) + + % remove specified edges + G.remove_edge(V1(j), V2(j)); + + % remove symmetric edges if subtracting symm graph + if symm + + G.remove_edge(V2(j), V1(j)); + end + end + + % --- + % Note : we only remove nodes with no + % remaining incoming edges + % --- + V = G2.nodes(); + for j = 1:numel(V) + + if G.degree(V(j)) == 0 + + G.remove_node(V(j)); + end + end + +end + +% --- +% compact graph representation +% --- +function [E, labels] = compact(G) + + % --- + % get nodes and create a reverse index + % --- + Vidx = sparse(G.nodes,1,1:G.order()); + [w, V1, V2] = G.edges(); + + % create compact adjacency matrix + E = sparse(Vidx(V1), Vidx(V2),w, G.order(), G.order()); + + labels = G.label(G.nodes()); +end + +% --- +% determines if Edges in G2 are the same as in G +% --- +function [out] = isSubgraph(G, G2) + + [val, V1, V2] = G2.edges(); + validE = false(numel(V1), 1); + + i = 1; + while i <= numel(V1) + + % --- + % Test if edge exists in other graph + % --- + if G.edge(V1(i),V2(i)) == val(i) + out = 0; + return; + end + i = i +1 ; + end + + % --- + % Test labels + % --- + if ~isempty(G.Vlbl) && ~isempty(G2.Vlbl) + + V = G2.nodes(); + i = 1; + while i <= numel(V) + if strcmp(G.label(V(i)), G2.label(V(i))) ~= 0 + out = 0; + return; + end + i = i + 1; + end + end + + out = 1; +end + +% --- +% Visualise the Similarity Graph +% --- +function visualise(G) + + % --- + % get colormap for edge weights + % --- + cmap = jet(100); + + % --- + % NOTE: we now get the weight colors for all edges + % get maximum weight and all edges + % --- + [colors, V1, V2] = G.edges(); + w = G.max_weight(); + + % normalise colors + colors = max(1,round((colors / w) * 100)); + + % get node labels + V1lbl = G.label(V1); + V2lbl = G.label(V2); + + % --- + % compose edgecolor matrix + % --- + ec = cell(numel(V1), 3); + for i = 1:numel(V1) + ec(i,:) = {V1lbl{i}, V2lbl{i}, cmap(colors(i),:)}; + end + + % --- + % For Performance Issues + % We get the compact Graph and + % !hope! for the labels to correspond to those above + % --- + [E, labels] = compact(G); + + % determine if symmetric Graph + clas = cat(1, class(G), superclasses(G)); + if strcellfind(clas, 'Graph') + symm = 1; + else + symm = 0; + end + + graphViz4Matlab('-adjMat',E, ... + '-nodeLabels',labels, ... + '-edgeColors', ec, ... + '-undirected', symm); +end +end + + +methods (Static) + function G = copy(Gin) + + if strcmp(class(Gin), 'DiGraph') + + G = DiGraph(Gin.V, Gin.E); + G.Vlbl = Gin.Vlbl; + + else + warning ('cannot copy graph, casting instead') + G = DiGraph.cast(Gin); + end + + end + + function G = cast(Gin) + + % --- + % this uses an imput grpaph + % to create a new digraph + % --- + G = DiGraph(); + + % --- + % Add the input graph to the empty one + % --- + G.add_graph(Gin); + + end +end +end \ No newline at end of file