Mercurial > hg > camir-aes2014
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 |