Skip to content

Commit 4a1c3f3

Browse files
Add DigraphContractEdge (#618)
* Add DigraphContractEdge * Add DigraphContractEdge list method * Last function was not supported by gap 4.10, Length used instead * Fixed issue for Immutable Digraphs * Last vertex (w) was incorrect due to Length being called on the initial graph * Add documentation, fixed in place list oper - Now uses IsDenseList for list arguments - List argument operation no longer expects a return value for Mutable Digraphs - Documentation added in oper.xml - Spelling mistake fixed in a comment in oper.gi * Fixed issue with not equal xml * Add include line in z-chap2.xml * Fixed errors caused by linting fixes * Correct xml docs - Removed comment in manual test example - Added line to manual test example of output - Corrected linting for oper.xml * Attempt to fix test coverage - Added tests for wrong list lengths * Make changes based on pull request comments * Edit manual test to reflect mutable return of digraph
1 parent bc224d1 commit 4a1c3f3

6 files changed

Lines changed: 605 additions & 0 deletions

File tree

doc/oper.xml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,55 @@ true
19201920
</ManSection>
19211921
<#/GAPDoc>
19221922

1923+
<#GAPDoc Label="DigraphContractEdge">
1924+
<ManSection>
1925+
<Oper Name="DigraphContractEdge" Arg="digraph, edge"
1926+
Label="for a digraph and a list of positive integers"/>
1927+
<Oper Name="DigraphContractEdge" Arg="digraph, src, ran"
1928+
Label="for a digraph and two positive integers"/>
1929+
<Returns>A digraph.</Returns>
1930+
<Description>
1931+
If <A>edge</A> is a pair of vertices of <A>digraph</A>, or <A>src</A> and
1932+
<A>ran</A> are vertices of <A>digraph</A>, where <A>ran</A> &lt;&gt; <A>src</A>,
1933+
then then this operation merges the two vertices of the edge given into one.
1934+
Edges incident to <A>src</A> and <A>ran</A> will now be incident to <C>v</C>,
1935+
the new vertex, with their direction preserved. <P/>
1936+
1937+
A new digraph constructed from <A>digraph</A> is returned,
1938+
unless <A>digraph</A> belongs to <Ref Filt="IsMutableDigraph"/>;
1939+
in this case changes are made directly to <A>digraph</A>, which is then returned.
1940+
The <A>digraph</A> must not belong to <Ref Filt="IsMultiDigraph"/>. <P/>
1941+
1942+
The labels of any remaining edges will be preserved. <P/>
1943+
1944+
Assigned vertex labels for <A>src</A> and <A>ran</A> are
1945+
combined into a list, and assigned to the new vertex <C>v</C>. <P/>
1946+
1947+
If an edge <A>[src, src]</A> or <A>[ran, ran]</A> exists,
1948+
a singular edge <C>[v, v]</C> is created. If edge <A>[ran, src]</A>
1949+
exists, this is also removed. <P/>
1950+
1951+
<Example><![CDATA[
1952+
gap> D := DigraphByEdges([[1, 2], [2, 1]]);
1953+
<immutable digraph with 2 vertices, 2 edges>
1954+
gap> D2 := DigraphContractEdge(D, 1, 2);
1955+
<immutable empty digraph with 1 vertex>
1956+
gap> DigraphEdges(D2);
1957+
[ ]
1958+
gap> D := DigraphByEdges(IsMutableDigraph, [[1, 2], [2, 3], [3, 4]]);
1959+
<mutable digraph with 4 vertices, 3 edges>
1960+
gap> DigraphVertexLabels(D);; # setting vertex labels
1961+
gap> DigraphContractEdge(D, [2, 3]);
1962+
<mutable digraph with 3 vertices, 2 edges>
1963+
gap> DigraphEdges(D);
1964+
[ [ 1, 3 ], [ 3, 2 ] ]
1965+
gap> DigraphVertexLabels(D);
1966+
[ 1, 4, [ 2, 3 ] ]
1967+
]]></Example>
1968+
</Description>
1969+
</ManSection>
1970+
<#/GAPDoc>
1971+
19231972
<#GAPDoc Label="IsMatching">
19241973
<ManSection>
19251974
<Oper Name="IsMatching" Arg="digraph, list"/>

doc/z-chap2.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<#Include Label="DigraphRemoveEdges">
5454
<#Include Label="DigraphRemoveLoops">
5555
<#Include Label="DigraphRemoveAllMultipleEdges">
56+
<#Include Label="DigraphContractEdge">
5657
<#Include Label="DigraphReverseEdges">
5758
<#Include Label="DigraphDisjointUnion">
5859
<#Include Label="DigraphEdgeUnion">

gap/oper.gd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ DeclareOperation("DigraphReverseEdges", [IsDigraph, IsList]);
3535

3636
DeclareOperation("DigraphClosure", [IsDigraph, IsPosInt]);
3737

38+
DeclareOperation("DigraphContractEdge", [IsDigraph, IsPosInt, IsPosInt]);
39+
DeclareOperation("DigraphContractEdge", [IsDigraph, IsDenseList]);
40+
3841
# 3. Ways of combining digraphs . . .
3942
DeclareGlobalFunction("DigraphDisjointUnion");
4043
DeclareGlobalFunction("DigraphJoin");

gap/oper.gi

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,232 @@ InstallMethod(DigraphClosure,
377377
[IsImmutableDigraph, IsPosInt],
378378
{D, k} -> MakeImmutable(DigraphClosure(DigraphMutableCopy(D), k)));
379379

380+
DIGRAPHS_CheckContractEdgeDigraph := function(D, u, v)
381+
if not IsDigraphEdge(D, u, v) then # Check if [u, v] is an edge in digraph D
382+
ErrorNoReturn("expected an edge between the 2nd and 3rd arguments ",
383+
"(vertices) ", u, " and ", v, " but found none");
384+
elif IsMultiDigraph(D) then
385+
ErrorNoReturn("The 1st argument (a digraph) must not satisfy ",
386+
"IsMultiDigraph");
387+
elif u = v then # edge should not be contracted if u = v
388+
ErrorNoReturn("The 2nd argument <u> must not be equal to the 3rd ",
389+
"argument <v>");
390+
fi;
391+
392+
end;
393+
394+
InstallMethod(DigraphContractEdge,
395+
"for a mutable digraph and two positive integers",
396+
[IsMutableDigraph, IsPosInt, IsPosInt],
397+
function(D, u, v)
398+
local w, neighbours, transformations_to_edge, transformations_to_old_edges,
399+
old_edge, new_edge, neighbour, t, vertices, edge_label;
400+
401+
DIGRAPHS_CheckContractEdgeDigraph(D, u, v);
402+
403+
# if (v, u) is an edge, disallow loops, so remove (v, u)
404+
if IsDigraphEdge(D, v, u) then
405+
DigraphRemoveEdge(D, v, u);
406+
fi;
407+
408+
# remove the contracted edge
409+
DigraphRemoveEdge(D, u, v);
410+
411+
# Find vertex neighbours of u and v to construct new incident edges of w
412+
413+
neighbours := [Immutable(InNeighboursOfVertex(D, u)),
414+
Immutable(InNeighboursOfVertex(D, v)),
415+
Immutable(OutNeighboursOfVertex(D, u)),
416+
Immutable(OutNeighboursOfVertex(D, v))];
417+
418+
# immutable reference to old edge labels to add to any
419+
# new transformed incident edges
420+
421+
DigraphAddVertex(D, [DigraphVertexLabel(D, u),
422+
DigraphVertexLabel(D, v)]); # add vertex w
423+
424+
vertices := DigraphVertices(D);
425+
w := Length(vertices); # w is the new vertex identifier
426+
427+
# Handle loops from edges u or w, with the same source / range
428+
# No relevant edges are removed from D until vertices are removed
429+
# So old edge labls are taken from D
430+
431+
if IsDigraphEdge(D, u, u) and IsDigraphEdge(D, v, v) then
432+
DigraphAddEdge(D, w, w);
433+
edge_label := [DigraphEdgeLabel(D, u, u), DigraphEdgeLabel(D, w, w)];
434+
SetDigraphEdgeLabel(D, w, w, edge_label);
435+
elif IsDigraphEdge(D, u, u) then
436+
DigraphAddEdge(D, w, w);
437+
edge_label := DigraphEdgeLabel(D, u, u);
438+
SetDigraphEdgeLabel(D, w, w, edge_label);
439+
elif IsDigraphEdge(D, v, v) then
440+
DigraphAddEdge(D, w, w);
441+
edge_label := DigraphEdgeLabel(D, v, v);
442+
SetDigraphEdgeLabel(D, w, w, edge_label);
443+
fi;
444+
445+
# translate edges based on neighbours
446+
447+
# transformation functions translating neighbours to their new edge
448+
transformations_to_edge := [{x, y} -> [x, y],
449+
{x, y} -> [x, y],
450+
{x, y} -> [y, x],
451+
{x, y} -> [y, x]];
452+
453+
# transformation functions translating neighbours
454+
# to their original edge in D
455+
456+
transformations_to_old_edges := [e -> [e, u],
457+
e -> [e, v],
458+
e -> [u, e],
459+
e -> [v, e]];
460+
461+
# Add translated new edges, and setup edge labels
462+
463+
for t in [1 .. Length(transformations_to_edge)] do
464+
for neighbour in neighbours[t] do
465+
new_edge := transformations_to_edge[t](neighbour, w);
466+
old_edge := transformations_to_old_edges[t](neighbour);
467+
edge_label := DigraphEdgeLabel(D, old_edge[1], old_edge[2]);
468+
DigraphAddEdge(D, new_edge);
469+
SetDigraphEdgeLabel(D, new_edge[1], new_edge[2], edge_label);
470+
od;
471+
od;
472+
473+
DigraphRemoveVertices(D, [u, v]); # remove the vertices of the
474+
# contracted edge (and therefore,
475+
# all its incident edges)
476+
return D;
477+
end);
478+
479+
InstallMethod(DigraphContractEdge,
480+
"for an immutable digraph and two positive integers",
481+
[IsImmutableDigraph, IsPosInt, IsPosInt],
482+
function(D, u, v)
483+
local w, neighbours, transformations_to_edge, transformations_to_old_edges,
484+
existing_edges, edges_to_not_include, new_digraph, new_edge, old_edge,
485+
neighbour, t, vertices, edge_label;
486+
487+
DIGRAPHS_CheckContractEdgeDigraph(D, u, v);
488+
489+
# Incident edges to [u, v] that will be transformed to be incident to w
490+
edges_to_not_include := [];
491+
492+
existing_edges := []; # existing edges that should be re added
493+
494+
# contracted edge should not be added to the new digraph
495+
new_digraph := NullDigraph(IsMutableDigraph, 0);
496+
497+
# if (v, u) is an edge, disallow loops, so remove (v, u)
498+
if IsDigraphEdge(D, v, u) then
499+
D := DigraphRemoveEdge(D, v, u);
500+
fi;
501+
502+
D := DigraphRemoveEdge(D, u, v); # remove the edge to be contracted
503+
504+
DigraphAddVertices(new_digraph, DigraphVertexLabels(D));
505+
506+
# add vertex w with combined labels from u and v
507+
DigraphAddVertex(new_digraph, [DigraphVertexLabel(D, u),
508+
DigraphVertexLabel(D, v)]); # add vertex w
509+
510+
vertices := DigraphVertices(new_digraph);
511+
w := Length(vertices); # w is the new vertex identifier
512+
513+
# Handle loops from edges u or w, with the same source / range
514+
515+
if IsDigraphEdge(D, u, u) and IsDigraphEdge(D, v, v) then
516+
DigraphAddEdge(new_digraph, w, w);
517+
SetDigraphEdgeLabel(new_digraph, w, w, [DigraphEdgeLabel(D, u, u),
518+
DigraphEdgeLabel(D, v, v)]);
519+
elif IsDigraphEdge(D, u, u) then
520+
DigraphAddEdge(new_digraph, w, w);
521+
SetDigraphEdgeLabel(new_digraph, w, w,
522+
DigraphEdgeLabel(D, u, u));
523+
elif IsDigraphEdge(D, v, v) then
524+
DigraphAddEdge(new_digraph, w, w);
525+
SetDigraphEdgeLabel(new_digraph, w, w, DigraphEdgeLabel(D, v, v));
526+
fi;
527+
528+
# if there were loops in D at the vertices u or w, don't include these edges,
529+
# but include a new loop at w
530+
531+
Append(edges_to_not_include, [[u, u], [v, v]]);
532+
533+
# Find vertex neighbours of u and v to construct new incident edges of w
534+
535+
neighbours := [InNeighboursOfVertex(D, u),
536+
InNeighboursOfVertex(D, v),
537+
OutNeighboursOfVertex(D, u),
538+
OutNeighboursOfVertex(D, v)];
539+
540+
# translate edges based on neighbours
541+
542+
# transformation functions translating neighbours to their new edge
543+
544+
transformations_to_edge := [{x, y} -> [x, y], {x, y} -> [x, y],
545+
{x, y} -> [y, x], {x, y} -> [y, x]];
546+
547+
# transformation functions translating neighbours to their original edge in D
548+
549+
transformations_to_old_edges := [e -> [e, u],
550+
e -> [e, v],
551+
e -> [u, e],
552+
e -> [v, e]];
553+
554+
# remove edges that will be adjusted
555+
556+
for t in [1 .. Length(transformations_to_old_edges)] do
557+
Append(edges_to_not_include, List(neighbours[t],
558+
transformations_to_old_edges[t]));
559+
od;
560+
561+
# Find edges that should be included, but remain the same
562+
563+
Sort(edges_to_not_include);
564+
Append(existing_edges, ShallowCopy(DigraphEdges(D)));
565+
Sort(existing_edges);
566+
SubtractSet(existing_edges, edges_to_not_include);
567+
568+
# Add translated new edges, and setup edge labels
569+
570+
for t in [1 .. Length(transformations_to_edge)] do
571+
for neighbour in neighbours[t] do
572+
new_edge := transformations_to_edge[t](neighbour, w);
573+
574+
# old_edge is what the new transformed edge was previously
575+
old_edge := transformations_to_old_edges[t](neighbour);
576+
edge_label := DigraphEdgeLabel(D, old_edge[1], old_edge[2]);
577+
new_digraph := DigraphAddEdge(new_digraph, new_edge);
578+
SetDigraphEdgeLabel(new_digraph, new_edge[1], new_edge[2], edge_label);
579+
od;
580+
od;
581+
582+
# Add the existing edges that have not changed,
583+
# and set their edge labels to that of the previous digraph
584+
585+
for new_edge in existing_edges do
586+
new_digraph := DigraphAddEdge(new_digraph, new_edge);
587+
SetDigraphEdgeLabel(new_digraph, new_edge[1], new_edge[2],
588+
DigraphEdgeLabel(D, new_edge[1], new_edge[2]));
589+
od;
590+
591+
# remove the vertices of the contracted edge
592+
return MakeImmutable(DigraphRemoveVertices(new_digraph, [u, v]));
593+
594+
end);
595+
596+
InstallMethod(DigraphContractEdge,
597+
"for a digraph and a dense list",
598+
[IsDigraph, IsDenseList],
599+
function(D, edge)
600+
if Length(edge) <> 2 then
601+
ErrorNoReturn("the 2nd argument <edge> must be a list of length 2");
602+
fi;
603+
return DigraphContractEdge(D, edge[1], edge[2]);
604+
end);
605+
380606
#############################################################################
381607
# 3. Ways of combining digraphs
382608
#############################################################################

0 commit comments

Comments
 (0)