comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:e9a9cd732c1e
1
2 % GENERAL GRAPH THEORY CLASS: DiGraph
3 %
4 % CAVE: Any special application oriented stuff
5 % should be outside here
6
7 classdef DiGraph < handle
8
9 properties
10
11 V = sparse(0,1); % boolean for nodes
12 Vlbl = {};
13
14 E = sparse(0,0); % sparse weighted edge graph
15 end
16
17 methods
18
19 % ---
20 % Constructor
21 % ---
22 function G = DiGraph(V, E, Vlbl)
23
24 if nargin == 1
25 if ~isnumeric(V)
26
27 G = DiGraph.copy(V);
28 else
29 % build graph from Edge Adjacency Matrix
30 G.V = sparse(find(sum(V + V')) > 0);
31 G.E = V;
32 end
33 end
34
35 if nargin >= 2
36 G = DiGraph();
37
38 if numel(V) ~= max(size(E))
39 error 'wrong dimensions';
40 end
41
42 % ---
43 % We copy existent nodes and edges
44 % ---
45 if numel(V) == size(V,2)
46 G.V = V;
47 else
48 G.V = V';
49 end
50 G.E = E;
51
52 % ---
53 % We copy the labels for existent nodes,
54 % if supplied.
55 % NOTE: the FIND call is neccessary, as logical indexing
56 % does not work in this case
57 % TODO: WHY NOT?
58 % ---
59 if (nargin == 3) && ~isempty(Vlbl)
60 G.Vlbl = cell(numel(G.V),1);
61 G.Vlbl(find(G.V)) = Vlbl(find(G.V));
62 end
63
64 end
65 end
66
67 % get list of node ids
68 function out = nodes(G)
69 out = find(G.V);
70 end
71
72 % get list of node ids
73 function out = last_node(G)
74 out = find(G.V,1,'last');
75 end
76
77 function w = edge(G, V1, V2)
78
79 w = G.E(V1, V2);
80 end
81
82 % get edge list representation
83 function [val, V1, V2] = edges(G, V1)
84
85 if nargin == 1
86
87 % get all edges of the graph
88 [V1, V2] = find(G.E);
89
90 val = zeros(numel(V1), 1);
91
92 for i = 1:numel(V1)
93 val(i) = G.E(V1(i), V2(i));
94 end
95
96 elseif nargin == 2
97
98 % get all edges coming from or entering this node
99 V2 = find(G.E(V1, :));
100 val = G.E(V1, V2);
101
102 V2 = [ V2 find(G.E(:,V1))'];
103 val = [ val G.E(V2, V1)'];
104 end
105 end
106
107 % compare two graphs
108 function out = eq(a,b)
109
110 % ---
111 % compare graph stats
112 % necessary
113 % ---
114 if a.order ~= b.order
115 out = false;
116 cprint(3, 'eq: graph node numbers do not match');
117 return
118 end
119
120 if a.num_edges ~= b.num_edges
121 out = false;
122 cprint(3, 'eq: graph edge numbers do not match');
123 return
124 end
125
126
127 % ---
128 % compare labels and reindex graph b if
129 % necessary
130 % ---
131 lbla = a.label();
132 lblb = b.label();
133
134 for i = 1:numel(lbla)
135 if ~isempty(lbla{i})
136 tmpidx = strcellfind(lblb, lbla{i});
137
138 % check for substring problems
139 for j = 1:numel(tmpidx)
140 if strcmp(lblb{tmpidx(j)}, lbla{i})
141 idx(i) = tmpidx(j);
142 break;
143 end
144 end
145 end
146 end
147
148 % test on found labels
149 num_matching_lbl = sum(idx > 0);
150 if ~isempty(lbla) && (num_matching_lbl < a.order)
151 out = false;
152 cprint(3, 'eq: graph labels do not match');
153 return
154 end
155
156 % ---
157 % reindex edges and nodes: only replace valid indexes
158 % ---
159 val_idx = idx > 0;
160 idx = idx(val_idx);
161
162 bE(val_idx,val_idx) = b.E(idx,idx);
163 bV(val_idx) = b.V(idx);
164
165 if sum(a.V ~= bV) > 0
166 out = false;
167 cprint(3, 'eq: nodes do not match');
168 return
169 end
170
171 if sum(sum(a.E ~= bE)) > 0
172 out = false;
173 cprint(3, 'eq: edges do not match');
174 return
175 end
176
177 % ---
178 % OK, seems these Graphs are the same!!!
179 % ---
180 out = true;
181
182 end
183
184 % find node via its label
185 function Vid = node(G, label)
186 lbl = G.label();
187
188 Vid = strcellfind(lbl, label,1);
189 end
190
191 % add a node
192 function add_node(G,V)
193
194 % set node indicator
195 G.V(V) = 1;
196
197 G.E(V,V) = 0;
198 end
199
200 % remove a node
201 function remove_node(G,V)
202
203 % remove node
204 G.V(V) = 0;
205
206 % remove all edges
207 G.E(V,:) = 0;
208 G.E(:,V) = 0;
209 end
210
211 % remove an edge
212 function remove_edge(G, V1, V2)
213
214 G.E(V1, V2) = 0;
215 end
216
217 % add an edge
218 function add_edge(G, V1, V2, weight)
219
220 G.add_node(V1);
221 G.add_node(V2);
222
223 if G.E(V1,V2) == 0
224
225 % save weight
226 set_edge(G, V1, V2, weight);
227 else
228
229 join_edge(G, V1, V2, weight)
230 end
231 end
232
233 % ---
234 % implementation of edge joining,
235 % to be overwritten for several
236 % purposes
237 % ---
238 function join_edge(G, V1, V2, weight)
239
240 set_edge(G, V1, V2, G.edge(V1, V2) + weight);
241 end
242
243 % ---
244 % sets the edge without considering earlier weights
245 % ---
246 function set_edge(G, V1, V2, weight)
247
248 G.E(V1, V2) = weight;
249 end
250
251 % ---
252 % Graph-specific functions
253 % ---
254 function c = children(G, Vid)
255
256 c = find(G.E(Vid,:) > 0);
257 end
258
259 function p = parents(G, Vid)
260
261 p = find(G.E(:,Vid) > 0)';
262 end
263
264 % ---
265 % Vertex Degree
266 % ---
267 function out = degree(G, V)
268
269 out = sum(G.E(V,:) > 0) + sum(G.E(:,V) > 0);
270 end
271
272 % ---
273 % Vertex Degree
274 % ---
275 function out = degree_in(G, V)
276
277 out = sum(G.E(V,:) > 0);
278 end
279
280 % ---
281 % Max Degree In
282 % ---
283 function out = max_degree_in(G)
284
285 out = max(sum(G.E > 0, 2));
286 end
287
288 % ---
289 % Vertex Degree
290 % ---
291 function out = degree_out(G, V)
292
293 out = sum(G.E(:,V) > 0);
294 end
295
296 % ---
297 % Max Degree In
298 % ---
299 function out = max_degree_out(G)
300
301 out = max(sum(G.E > 0, 1));
302 end
303
304
305 % ---
306 % Max Degree
307 % ---
308 function out = max_degree(G)
309
310 out = max(sum(G.E > 0, 1) + sum(G.E > 0, 2)');
311 end
312
313 % ---
314 % Max weight
315 % ---
316 function out = max_weight(G)
317
318 out = max(max(G.E));
319 end
320
321 % ---
322 % Number of Vertices in Graph
323 % ---
324 function out = order(G)
325 out = sum(G.V);
326 end
327
328 % ---
329 % Number of Edges in Graph
330 % ---
331 function out = num_edges(G)
332 out = sum(sum(G.E ~= 0));
333 end
334
335 % ---
336 % Number of Vertices in Graph
337 % ---
338 function out = cardinality(G)
339 out = order(G);
340 end
341
342 function Gs = unconnected_subgraph(G)
343 Vtmp = (sum(G.E + G.E') == 0);
344 Etmp = sparse(size(G.E, 1), size(G.E, 1));
345
346 Gs = DiGraph(Vtmp, Etmp, G.label());
347 end
348
349 % ---
350 % return string labels for a (set of) node(S)
351 % ---
352 function out = label(G, Vid)
353
354 out = {};
355 if nargin == 1
356 % maybe much faster for whole graph
357 if (numel(G.Vlbl) < G.order() || isempty(G.Vlbl))
358
359 out = cell(numel(G.V), 1);
360 for i = 1:numel(Vid)
361 out{i} = sprintf('%d', Vid(i));
362 end
363 else
364 out = G.Vlbl;
365 end
366
367 elseif nargin == 2
368 for i = 1:numel(Vid)
369
370 if (numel(G.Vlbl) < Vid(i)) || isempty(G.Vlbl{Vid(i)})
371
372 if numel(Vid) > 1
373 out{i} = sprintf('%d', Vid(i));
374 else
375 out = sprintf('%d', Vid(i));
376 end
377 else
378 if numel(Vid) > 1
379 out{i} = G.Vlbl{Vid(i)};
380 else
381 out = G.Vlbl{Vid(i)};
382 end
383 end
384 end
385 end
386 end
387
388
389 % -----------------------------------------------------------------
390 % Graph theory algorithms
391 % ---
392
393
394 % ---
395 % sets all the main diagonal edges to zero
396 % ---
397 function remove_cycles_length1(G)
398
399 for i = G.nodes();
400 G.E(i,i) = 0;
401 end
402 end
403
404
405 % ---
406 % Returns whether a given graph has cycles or not,
407 % using the SCC search
408 % ---
409 function out = isAcyclic(G)
410 % ---
411 % We get all the sccs in the DiGraph, and
412 % return true if none of the sccs has more than
413 % one node
414 % ---
415
416 % check for self-loops
417 if sum(abs(diag(G.E))) > 0
418 out = 0;
419 return;
420 end
421
422 [~, s, ~] = strongly_connected_components(G);
423
424 if max(s) < 2
425 out = 1;
426 else
427 out = 0;
428 end
429 end
430
431 % ---
432 % All SCC's ordered by number of nodes in graph
433 %
434 % this should also be able to return
435 % the SCC of just one node
436 % ---
437 function [Gs, s, id] = strongly_connected_components(G, Vin)
438
439 % marking
440 marked = zeros(size(G.V));
441
442 % ---
443 % two stacks,
444 % one: has not been assigned to scc
445 % two: unclear if in same scc
446 % ---
447 stack1 = CStack();
448 stack2 = CStack();
449
450 % initialise scc ids
451 id = zeros(size(G.V)) - 1; % assigned scc?
452 idctr = 1;
453
454 % initialise graph ordering
455 preorder = zeros(G.order, 1);
456 prectr = 0;
457
458 for v = G.nodes();
459 if ~marked(v)
460 dfs(G, v);
461 end
462 end
463
464 % ---
465 % create subgraphs (DiGraph here) for sccs
466 % ---
467 if nargin == 1
468
469 s = zeros(idctr-1,1);
470 for idctr2 = 1:idctr-1
471 Vtmp = (id == idctr2);
472 Emask = sparse(size(G.E, 1), size(G.E, 1));
473 Emask(Vtmp,Vtmp) = 1;
474 Etmp = G.E .* Emask;
475
476 Gs(idctr2) = DiGraph(Vtmp, Etmp, G.Vlbl);
477 s(idctr2) = Gs(idctr2).order();
478 end
479
480 % ---
481 % order by number of nodes
482 % ---
483 [s, idx] = sort(s,'descend');
484 Gs = Gs(idx);
485 Gmax = Gs(1);
486
487 else
488 % ---
489 % get just the scc for the questioned node
490 % ---
491 Vtmp = (id == id(Vin));
492 Emask = sparse(size(G.E, 1), size(G.E, 1));
493 Emask(Vtmp,Vtmp) = 1;
494 Etmp = G.E .* Emask;
495
496 Gs = DiGraph(Vtmp, Etmp);
497 s = Gs.order();
498 end
499
500 % ---
501 % NOTE: THIS IS A NESTED DFS BASED GRAPH ORDERING
502 % ---
503 function dfs(G, v)
504
505 % mark this node as visited
506 marked(v) = 1;
507
508 preorder(v) = prectr;
509 prectr = prectr + 1;
510
511 % push into both stacks
512 stack1.push(v);
513 stack2.push(v);
514
515 % ---
516 % dfs
517 % ---
518 for w = G.children(v)
519 if ~marked(w)
520 % ---
521 % traverse into dfs if not yet visited
522 % ---
523 dfs(G, w);
524
525 elseif id(w) == -1
526
527 % ---
528 % w has not yet been assigned to a strongly connected
529 % component
530 % ---
531 while ((preorder(stack2.top()) > preorder(w)))
532 stack2.pop();
533 end
534 end
535 end
536
537 % ---
538 % found scc containing v
539 % ---
540 if (stack2.top() == v)
541 stack2.pop();
542
543 w = -1;
544 while (w ~= v)
545
546 % ---
547 % collect all the nodes of this scc
548 % ---
549 w = stack1.pop();
550 id(w) = idctr;
551 end
552 idctr = idctr + 1;
553 end
554
555 end % function dfs
556 end
557
558 function [Gs, s, id] = connected_components(G, varargin)
559 % ---
560 % get all connected subgraphs:
561 % ---
562
563 % make edge matrix undirected
564 G2 = DiGraph(Graph(G));
565
566 [GsGraph, s, id] = strongly_connected_components(G2, varargin{:});
567
568 % get the actual subgraps
569
570 for i =1:numel(GsGraph)
571 Gs(i) = G.subgraph(GsGraph(i).nodes);
572 end
573 end
574
575 % ---
576 % creates new graph just containing the
577 % specified nodes and optionally specified edges
578 % nodes can be specified using
579 % a. the binary G.V structure
580 % b. or node indices
581 % ---
582 function G2 = subgraph(G, V, E)
583 if nargin == 2
584 E = [];
585 end
586
587 % ---
588 % create new graph as copy ofthe old
589 % ---
590
591 G2 = feval(class(G), G);
592
593 % ---
594 % reset nodes and edges
595 % NOTE: we determine the input format first
596 % ---
597 if (max(V) == 1 && numel(V) > 1) || max(V) == 0
598
599 G2.remove_node(find(~V));
600 else
601
602 G2.remove_node(setdiff(1:numel(G.V), V));
603 end
604 if ~isempty(E)
605 G2.E = E;
606 end
607 end
608
609 % ---
610 % joins the information of graph G2 with
611 % this GRaph, not duplicating nodes.
612 % ---
613 function add_graph(G, G2)
614
615 % determine if symmetric edges have to be added
616 clas = cat(1, class(G2), superclasses(G2));
617 if strcellfind(clas, 'Graph')
618 add_symm = 1;
619 else
620 add_symm = 0;
621 end
622
623 % ---
624 % add all nodes and edges in G2
625 % ---
626 for V = G2.nodes();
627
628 G.add_node(V);
629 end
630
631 % ---
632 % NOTE / TODO:
633 % this LABEL inheritance is far too expensive when
634 % creating many copiesof the same graph
635 % Its also unnessecary for lots of them
636 % except for debugging :(.
637 % ---
638 G.Vlbl = cell(1,numel(G2.V));
639 G.Vlbl(G2.nodes()) = G2.label(G2.nodes());
640
641 % ---
642 % get all edges in G2
643 % ---
644 [val, V1, V2] = edges(G2);
645
646 for i = 1:numel(val)
647
648 % ---
649 % add edges to graph
650 % ---
651 G.add_edge(V1(i), V2(i), val(i));
652
653 if add_symm
654 % ---
655 % add symmetric edges to graph
656 % ---
657 G.add_edge(V2(i), V1(i), val(i));
658 end
659
660 end
661 end
662
663 % ---
664 % substracts the edges in G2 from
665 % this GRaph, removing not connected nodes.
666 % ---
667 function Gout = minus(a, b)
668 Gout = feval(class(a),a);
669 Gout.remove_graph(b);
670 end
671
672 function remove_graph(G, G2)
673
674 % determine if symmetric edges have to be added
675 clas = cat(1, class(G2), superclasses(G2));
676 if strcellfind(clas, 'Graph')
677 symm = 1;
678 else
679 symm = 0;
680 end
681
682 % remove edges
683 [val, V1, V2] = G2.edges();
684 for j = 1:numel(val)
685
686 % remove specified edges
687 G.remove_edge(V1(j), V2(j));
688
689 % remove symmetric edges if subtracting symm graph
690 if symm
691
692 G.remove_edge(V2(j), V1(j));
693 end
694 end
695
696 % ---
697 % Note : we only remove nodes with no
698 % remaining incoming edges
699 % ---
700 V = G2.nodes();
701 for j = 1:numel(V)
702
703 if G.degree(V(j)) == 0
704
705 G.remove_node(V(j));
706 end
707 end
708
709 end
710
711 % ---
712 % compact graph representation
713 % ---
714 function [E, labels] = compact(G)
715
716 % ---
717 % get nodes and create a reverse index
718 % ---
719 Vidx = sparse(G.nodes,1,1:G.order());
720 [w, V1, V2] = G.edges();
721
722 % create compact adjacency matrix
723 E = sparse(Vidx(V1), Vidx(V2),w, G.order(), G.order());
724
725 labels = G.label(G.nodes());
726 end
727
728 % ---
729 % determines if Edges in G2 are the same as in G
730 % ---
731 function [out] = isSubgraph(G, G2)
732
733 [val, V1, V2] = G2.edges();
734 validE = false(numel(V1), 1);
735
736 i = 1;
737 while i <= numel(V1)
738
739 % ---
740 % Test if edge exists in other graph
741 % ---
742 if G.edge(V1(i),V2(i)) == val(i)
743 out = 0;
744 return;
745 end
746 i = i +1 ;
747 end
748
749 % ---
750 % Test labels
751 % ---
752 if ~isempty(G.Vlbl) && ~isempty(G2.Vlbl)
753
754 V = G2.nodes();
755 i = 1;
756 while i <= numel(V)
757 if strcmp(G.label(V(i)), G2.label(V(i))) ~= 0
758 out = 0;
759 return;
760 end
761 i = i + 1;
762 end
763 end
764
765 out = 1;
766 end
767
768 % ---
769 % Visualise the Similarity Graph
770 % ---
771 function visualise(G)
772
773 % ---
774 % get colormap for edge weights
775 % ---
776 cmap = jet(100);
777
778 % ---
779 % NOTE: we now get the weight colors for all edges
780 % get maximum weight and all edges
781 % ---
782 [colors, V1, V2] = G.edges();
783 w = G.max_weight();
784
785 % normalise colors
786 colors = max(1,round((colors / w) * 100));
787
788 % get node labels
789 V1lbl = G.label(V1);
790 V2lbl = G.label(V2);
791
792 % ---
793 % compose edgecolor matrix
794 % ---
795 ec = cell(numel(V1), 3);
796 for i = 1:numel(V1)
797 ec(i,:) = {V1lbl{i}, V2lbl{i}, cmap(colors(i),:)};
798 end
799
800 % ---
801 % For Performance Issues
802 % We get the compact Graph and
803 % !hope! for the labels to correspond to those above
804 % ---
805 [E, labels] = compact(G);
806
807 % determine if symmetric Graph
808 clas = cat(1, class(G), superclasses(G));
809 if strcellfind(clas, 'Graph')
810 symm = 1;
811 else
812 symm = 0;
813 end
814
815 graphViz4Matlab('-adjMat',E, ...
816 '-nodeLabels',labels, ...
817 '-edgeColors', ec, ...
818 '-undirected', symm);
819 end
820 end
821
822
823 methods (Static)
824 function G = copy(Gin)
825
826 if strcmp(class(Gin), 'DiGraph')
827
828 G = DiGraph(Gin.V, Gin.E);
829 G.Vlbl = Gin.Vlbl;
830
831 else
832 warning ('cannot copy graph, casting instead')
833 G = DiGraph.cast(Gin);
834 end
835
836 end
837
838 function G = cast(Gin)
839
840 % ---
841 % this uses an imput grpaph
842 % to create a new digraph
843 % ---
844 G = DiGraph();
845
846 % ---
847 % Add the input graph to the empty one
848 % ---
849 G.add_graph(Gin);
850
851 end
852 end
853 end