diff --git a/doc/oper.xml b/doc/oper.xml
index 0930e8f3c..d19ca3973 100644
--- a/doc/oper.xml
+++ b/doc/oper.xml
@@ -2076,6 +2076,84 @@ gap> DigraphVertexLabels(D);
<#/GAPDoc>
+<#GAPDoc Label="DigraphInsertEdge">
+
+
+ A digraph.
+
+ If edge1 and edge2 are distinct pairs of vertices of digraph,
+ then then this operation inserts a new edge between edge1 and
+ edge2.
+ edge1 and edge2 are split up into two new edges each, which are
+ all adjacent to the inserted edge. Hence, the vertices of the inserted edge both
+ have a degree of 3.
+
+ The opposite operation is .
+
+ A new digraph constructed from digraph is returned,
+ unless digraph belongs to ;
+ in this case changes are made directly to digraph, which is then returned.
+ The digraph must not belong to .
+
+ D := DigraphByEdges([[1, 2], [2, 1], [3, 4], [4, 3]]);
+
+gap> D2 := DigraphInsertEdge(D, [1, 2], [3, 4]);
+
+gap> DigraphEdges(D2);
+[ [ 1, 5 ], [ 2, 5 ], [ 3, 6 ], [ 4, 6 ], [ 5, 1 ], [ 5, 2 ], [ 5, 6 ],
+ [ 6, 3 ], [ 6, 4 ], [ 6, 5 ] ]
+gap> D := DigraphByEdges([[1, 2], [2, 1], [2, 3], [3, 2]]);
+
+gap> D2 := DigraphInsertEdge(D, [1, 2], [2, 3]);
+
+gap> DigraphEdges(D2);
+[ [ 1, 4 ], [ 2, 4 ], [ 2, 5 ], [ 3, 5 ], [ 4, 1 ], [ 4, 2 ], [ 4, 5 ],
+ [ 5, 2 ], [ 5, 3 ], [ 5, 4 ] ]
+]]>
+
+
+<#/GAPDoc>
+
+<#GAPDoc Label="DigraphReduceEdge">
+
+
+ A digraph.
+
+ If edge is an edge of digraph and adjacent to four edges, then this
+ operation reduces this edge: edge will be removed, and for both
+ vertices of edge the two adjacent edges are combined to a single edge.
+ The vertices of edge must both have a degree of 3.
+
+ The opposite operation is .
+
+ A new digraph constructed from digraph is returned,
+ unless digraph belongs to ;
+ in this case changes are made directly to digraph, which is then returned.
+ The digraph must not belong to .
+
+ D := DigraphByEdges([[1, 5], [5, 1], [2, 5], [5, 2], [3, 6],
+ [6, 3], [4, 6], [6, 4], [5, 6], [6, 5]]);
+
+gap> D2 := DigraphReduceEdge(D, [5, 6]);
+
+gap> DigraphEdges(D2);
+[ [ 1, 2 ], [ 2, 1 ], [ 3, 4 ], [ 4, 3 ] ]
+gap> D := DigraphByEdges([[1, 4], [4, 1], [2, 4], [4, 2], [2, 5],
+ [5, 2], [3, 5], [5, 3], [4, 5], [5, 4]]);
+
+gap> D2 := DigraphReduceEdge(D, [4, 5]);
+
+gap> DigraphEdges(D2);
+[ [ 1, 2 ], [ 2, 1 ], [ 2, 3 ], [ 3, 2 ] ]
+]]>
+
+
+<#/GAPDoc>
+
<#GAPDoc Label="IsMatching">
diff --git a/gap/oper.gd b/gap/oper.gd
index f6a37e0f8..722439753 100644
--- a/gap/oper.gd
+++ b/gap/oper.gd
@@ -38,6 +38,10 @@ DeclareOperation("DigraphClosure", [IsDigraph, IsPosInt]);
DeclareOperation("DigraphContractEdge", [IsDigraph, IsPosInt, IsPosInt]);
DeclareOperation("DigraphContractEdge", [IsDigraph, IsDenseList]);
+DeclareOperation("DigraphInsertEdge", [IsDigraph, IsDenseList, IsDenseList]);
+
+DeclareOperation("DigraphReduceEdge", [IsDigraph, IsDenseList]);
+
# 3. Ways of combining digraphs . . .
DeclareGlobalFunction("DigraphDisjointUnion");
DeclareGlobalFunction("DigraphJoin");
diff --git a/gap/oper.gi b/gap/oper.gi
index 3876db50e..6e26bcbc6 100644
--- a/gap/oper.gi
+++ b/gap/oper.gi
@@ -603,6 +603,187 @@ function(D, edge)
return DigraphContractEdge(D, edge[1], edge[2]);
end);
+DIGRAPHS_CheckInsertEdgeDigraph := function(D, edge1, edge2)
+ if IsEmptyDigraph(D) then
+ ErrorNoReturn("the 1st argument must not be an empty digraph");
+ fi;
+ if not IsSymmetricDigraph(D) then
+ ErrorNoReturn("the 1st argument must be a symmetric digraph");
+ fi;
+
+ if Length(edge1) <> 2 then
+ ErrorNoReturn("the 2nd argument must be a list of length 2");
+ fi;
+ if not IsDigraphEdge(D, edge1) then
+ ErrorNoReturn("the 2nd argument must be an edge of the digraph ");
+ fi;
+
+ if Length(edge2) <> 2 then
+ ErrorNoReturn("the 3rd argument must be a list of length 2");
+ fi;
+ if not IsDigraphEdge(D, edge2) then
+ ErrorNoReturn("the 3rd argument must be an edge of the digraph ");
+ fi;
+
+ if Length(Union(edge1, edge2)) < 3 then
+ ErrorNoReturn("the 2nd and 3rd argument and must be two different edges");
+ fi;
+
+end;
+
+InstallMethod(DigraphInsertEdge,
+"for a symmetric digraph and two dense lists",
+[IsMutableDigraph, IsDenseList, IsDenseList],
+function(D, edge1, edge2)
+ local numVertices;
+
+ DIGRAPHS_CheckInsertEdgeDigraph(D, edge1, edge2);
+
+ numVertices := Maximum(DigraphVertices(D));
+
+ D := DigraphRemoveEdge(D, edge1);
+ D := DigraphRemoveEdge(D, Reversed(edge1));
+ D := DigraphRemoveEdge(D, edge2);
+ D := DigraphRemoveEdge(D, Reversed(edge2));
+
+ DigraphAddVertex(D, numVertices + 1); # vertex A
+ DigraphAddVertex(D, numVertices + 2); # vertex B
+
+ # Add two connected edges between each former edge
+ #
+ # Add edges intersecting former edge1 with vertex A
+ DigraphAddEdge(D, edge1[1], numVertices + 1);
+ DigraphAddEdge(D, numVertices + 1, edge1[1]);
+ #
+ DigraphAddEdge(D, edge1[2], numVertices + 1);
+ DigraphAddEdge(D, numVertices + 1, edge1[2]);
+ #
+ # Add edges intersecting former edge2 with vertex B
+ DigraphAddEdge(D, edge2[1], numVertices + 2);
+ DigraphAddEdge(D, numVertices + 2, edge2[1]);
+ #
+ DigraphAddEdge(D, edge2[2], numVertices + 2);
+ DigraphAddEdge(D, numVertices + 2, edge2[2]);
+ #
+ # Add edges connecting vertex A and vertex B
+ DigraphAddEdge(D, numVertices + 1, numVertices + 2);
+ DigraphAddEdge(D, numVertices + 2, numVertices + 1);
+
+ # TODO: Call SetIsSymmetricDigraph and/or
+ # SetDigraphSymmetricClosureAttr here?
+
+ return D;
+end);
+
+InstallMethod(DigraphInsertEdge,
+"for a symmetric digraph and two dense lists",
+[IsImmutableDigraph, IsDenseList, IsDenseList],
+function(D, edge1, edge2)
+ D := DigraphInsertEdge(DigraphMutableCopy(D), edge1, edge2);
+
+ return MakeImmutable(D);
+end);
+
+InstallMethod(DigraphInsertEdge,
+"for a symmetric digraph and two dense lists",
+[IsDigraph, IsDenseList, IsDenseList],
+function(D, edge1, edge2)
+ return DigraphInsertEdge(D, edge1, edge2);
+end);
+
+DIGRAPHS_CheckReduceEdgeDigraph := function(D, edge)
+ local leftVertexOutNeighbours, rightVertexOutNeighbours;
+
+ if IsEmptyDigraph(D) then
+ ErrorNoReturn("the 1st argument must not be an empty digraph");
+ fi;
+ if not IsSymmetricDigraph(D) then
+ ErrorNoReturn("the 1st argument must be a symmetric digraph");
+ fi;
+
+ if Length(edge) <> 2 then
+ ErrorNoReturn("the 2nd argument must be a list of length 2");
+ fi;
+ if not IsDigraphEdge(D, edge) then
+ ErrorNoReturn("the 2nd argument must be an edge of the digraph ");
+ fi;
+
+ leftVertexOutNeighbours := OutNeighboursOfVertex(D, edge[1]);
+ rightVertexOutNeighbours := OutNeighboursOfVertex(D, edge[2]);
+
+ # Check if edge vertices have degree three
+ if Length(leftVertexOutNeighbours) <> 3 or Length(rightVertexOutNeighbours) <> 3 then
+ ErrorNoReturn("the 2nd argument must be an edge where the incident vertices have degree three");
+ fi;
+ if not Length(Union(leftVertexOutNeighbours, rightVertexOutNeighbours)) in [5, 6] then
+ ErrorNoReturn("the 2nd argument must be an edge that is adjacent to four edges");
+ fi;
+
+end;
+
+InstallMethod(DigraphReduceEdge,
+"for a symmetric digraph and a dense list",
+[IsMutableDigraph, IsDenseList],
+function(D, edge)
+ local neighbours, neighbour, newEdge;
+
+ DIGRAPHS_CheckReduceEdgeDigraph(D, edge);
+
+ # Add new edges
+ #
+ # left side of edge
+ newEdge := [];
+ neighbours := OutNeighboursOfVertex(D, edge[1]);
+ for neighbour in neighbours do
+ if neighbour <> edge[2] then
+ Add(newEdge, neighbour);
+ fi;
+ od;
+ for neighbour in neighbours do
+ DigraphRemoveEdge(D, [edge[1], neighbour]);
+ od;
+ DigraphAddEdge(D, newEdge[1], newEdge[2]);
+ DigraphAddEdge(D, newEdge[2], newEdge[1]);
+ #
+ # right side of intersecting edge
+ newEdge := [];
+ neighbours := OutNeighboursOfVertex(D, edge[2]);
+ for neighbour in neighbours do
+ if neighbour <> edge[1] then
+ Add(newEdge, neighbour);
+ fi;
+ od;
+ for neighbour in neighbours do
+ DigraphRemoveEdge(D, [edge[2], neighbour]);
+ od;
+ DigraphAddEdge(D, newEdge[1], newEdge[2]);
+ DigraphAddEdge(D, newEdge[2], newEdge[1]);
+
+ # Remove old vertices
+ DigraphRemoveVertices(D, edge);
+
+ # TODO: Call SetIsSymmetricDigraph and/or
+ # SetDigraphSymmetricClosureAttr here?
+
+ return D;
+end);
+
+InstallMethod(DigraphReduceEdge,
+"for a symmetric digraph and a dense list",
+[IsImmutableDigraph, IsDenseList],
+function(D, edge)
+ D := DigraphReduceEdge(DigraphMutableCopy(D), edge);
+
+ return MakeImmutable(D);
+end);
+
+InstallMethod(DigraphReduceEdge,
+"for a symmetric digraph and a dense list",
+[IsDigraph, IsDenseList],
+function(D, edge)
+ return DigraphReduceEdge(D, edge);
+end);
+
#############################################################################
# 3. Ways of combining digraphs
#############################################################################
diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst
index 1c813fe21..401573fda 100644
--- a/tst/standard/oper.tst
+++ b/tst/standard/oper.tst
@@ -3324,6 +3324,177 @@ gap> DigraphEdges(D);
gap> DigraphVertexLabels(D);
[ 1, 2, 3, 6, [ 4, 5 ] ]
+# DigraphInsertEdge
+
+# DigraphInsertEdge: D is empty digraph test
+gap> D := NullDigraph(0);
+
+gap> DigraphInsertEdge(D, [1,2], [3,4]);
+Error, the 1st argument must not be an empty digraph
+
+# DigraphInsertEdge: D is not a symmetric digraph
+gap> D := DigraphByEdges([[1,2], [3,4], [1,3], [2,4]]);
+
+gap> DigraphInsertEdge(D, [1,2], [3,4]);
+Error, the 1st argument must be a symmetric digraph
+
+# DigraphInsertEdge: edge1 is not a list of length 2
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [], [3,4]);
+Error, the 2nd argument must be a list of length 2
+gap> DigraphInsertEdge(D, [1], [3,4]);
+Error, the 2nd argument must be a list of length 2
+gap> DigraphInsertEdge(D, [1,2,3], [3,4]);
+Error, the 2nd argument must be a list of length 2
+
+# DigraphInsertEdge: edge2 is not a list of length 2
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [1,2], []);
+Error, the 3rd argument must be a list of length 2
+gap> DigraphInsertEdge(D, [1,2], [3]);
+Error, the 3rd argument must be a list of length 2
+gap> DigraphInsertEdge(D, [1,2], [2,3,4]);
+Error, the 3rd argument must be a list of length 2
+
+# DigraphInsertEdge: edge1 is not an edge in D
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [1,4], [3,4]);
+Error, the 2nd argument must be an edge of the digraph
+gap> DigraphInsertEdge(D, [5,6], [3,4]);
+Error, the 2nd argument must be an edge of the digraph
+
+# DigraphInsertEdge: edge2 is not an edge in D
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [1,2], [3,2]);
+Error, the 3rd argument must be an edge of the digraph
+gap> DigraphInsertEdge(D, [1,2], [5,6]);
+Error, the 3rd argument must be an edge of the digraph
+
+# DigraphInsertEdge: edge1 and edge2 are identical
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [1,2], [1,2]);
+Error, the 2nd and 3rd argument and must be two different edge\
+s
+
+# DigraphInsertEdge: standard insertion (mutable digraph D)
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [1,2], [3,4]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 5 ], [ 2, 4 ], [ 2, 5 ], [ 3, 1 ], [ 3, 6 ], [ 4, 2 ],
+ [ 4, 6 ], [ 5, 1 ], [ 5, 2 ], [ 5, 6 ], [ 6, 3 ], [ 6, 4 ], [ 6, 5 ] ]
+
+# DigraphInsertEdge: standard insertion (immutable digraph D)
+gap> D := DigraphByEdges(IsImmutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> D := DigraphInsertEdge(D, [1,2], [3,4]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 5 ], [ 2, 4 ], [ 2, 5 ], [ 3, 1 ], [ 3, 6 ], [ 4, 2 ],
+ [ 4, 6 ], [ 5, 1 ], [ 5, 2 ], [ 5, 6 ], [ 6, 3 ], [ 6, 4 ], [ 6, 5 ] ]
+
+# DigraphInsertEdge: triangle insertion (mutable digraph D)
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [1,2], [2,4]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 5 ], [ 2, 5 ], [ 2, 6 ], [ 3, 4 ], [ 3, 1 ], [ 4, 3 ],
+ [ 4, 6 ], [ 5, 1 ], [ 5, 2 ], [ 5, 6 ], [ 6, 2 ], [ 6, 4 ], [ 6, 5 ] ]
+
+# DigraphInsertEdge: triangle insertion (immutable digraph D)
+gap> D := DigraphByEdges(IsImmutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> D := DigraphInsertEdge(D, [1,2], [2,4]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 5 ], [ 2, 5 ], [ 2, 6 ], [ 3, 4 ], [ 3, 1 ], [ 4, 3 ],
+ [ 4, 6 ], [ 5, 1 ], [ 5, 2 ], [ 5, 6 ], [ 6, 2 ], [ 6, 4 ], [ 6, 5 ] ]
+
+# DigraphReduceEdge
+
+# DigraphReduceEdge: D is empty digraph test
+gap> D := NullDigraph(0);
+
+gap> DigraphReduceEdge(D, [1,2]);
+Error, the 1st argument must not be an empty digraph
+
+# DigraphReduceEdge: D is not a symmetric digraph
+gap> D := DigraphByEdges([[1,2], [3,4], [1,3], [2,4]]);
+
+gap> DigraphReduceEdge(D, [1,2]);
+Error, the 1st argument must be a symmetric digraph
+
+# DigraphReduceEdge: edge is not a list of length 2
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphReduceEdge(D, []);
+Error, the 2nd argument must be a list of length 2
+gap> DigraphReduceEdge(D, [1]);
+Error, the 2nd argument must be a list of length 2
+gap> DigraphReduceEdge(D, [1,2,2]);
+Error, the 2nd argument must be a list of length 2
+
+# DigraphReduceEdge: edge is not an edge in D
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphReduceEdge(D, [1,4]);
+Error, the 2nd argument must be an edge of the digraph
+gap> DigraphReduceEdge(D, [5,6]);
+Error, the 2nd argument must be an edge of the digraph
+
+# DigraphReduceEdge: vertex at edge[1] or edge[2] does not have a degree of 3
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,3], [3,1], [2,4], [4,2], [1,5], [5,1], [2,5], [5,2], [3,6], [6,3], [4,6], [6,4], [5,6], [6,5]]);
+
+gap> DigraphReduceEdge(D, [4,6]);
+Error, the 2nd argument must be an edge where the incident vertices hav\
+e degree three
+gap> DigraphReduceEdge(D, [6,4]);
+Error, the 2nd argument must be an edge where the incident vertices hav\
+e degree three
+
+# DigraphReduceEdge: standard reduction (mutable digraph D)
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,3], [3,1], [2,4], [4,2], [1,5], [5,1], [2,5], [5,2], [3,6], [6,3], [4,6], [6,4], [5,6], [6,5]]);
+
+gap> DigraphReduceEdge(D, [5,6]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 2 ], [ 2, 4 ], [ 2, 1 ], [ 3, 1 ], [ 3, 4 ], [ 4, 2 ],
+ [ 4, 3 ] ]
+
+# DigraphReduceEdge: standard reduction (immutable digraph D)
+gap> D := DigraphByEdges(IsImmutableDigraph, [[1,3], [3,1], [2,4], [4,2], [1,5], [5,1], [2,5], [5,2], [3,6], [6,3], [4,6], [6,4], [5,6], [6,5]]);
+
+gap> D := DigraphReduceEdge(D, [5,6]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 2 ], [ 2, 4 ], [ 2, 1 ], [ 3, 1 ], [ 3, 4 ], [ 4, 2 ],
+ [ 4, 3 ] ]
+
+# DigraphReduceEdge: triangle reduction (mutable digraph D)
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,3], [3,1], [1,5], [5,1], [2,5], [5,2], [2,6], [6,2], [3,4], [4,3], [4,6], [6,4], [5,6], [6,5]]);
+
+gap> DigraphReduceEdge(D, [5,6]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 2 ], [ 2, 1 ], [ 2, 4 ], [ 3, 1 ], [ 3, 4 ], [ 4, 3 ],
+ [ 4, 2 ] ]
+
+# DigraphReduceEdge: triangle reduction (immutable digraph D)
+gap> D := DigraphByEdges(IsImmutableDigraph, [[1,3], [3,1], [1,5], [5,1], [2,5], [5,2], [2,6], [6,2], [3,4], [4,3], [4,6], [6,4], [5,6], [6,5]]);
+
+gap> D := DigraphReduceEdge(D, [5,6]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 2 ], [ 2, 1 ], [ 2, 4 ], [ 3, 1 ], [ 3, 4 ], [ 4, 3 ],
+ [ 4, 2 ] ]
+
#
gap> DIGRAPHS_StopTest();
gap> STOP_TEST("Digraphs package: standard/oper.tst", 0);
diff --git a/tst/testinstall.tst b/tst/testinstall.tst
index 291b09b7a..6afa8a2da 100644
--- a/tst/testinstall.tst
+++ b/tst/testinstall.tst
@@ -475,6 +475,24 @@ gap> C := DigraphContractEdge(D, 2, 1);
gap> DigraphEdges(C);
[ [ 2, 1 ] ]
+# DigraphInsertEdge
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,2], [2,1], [3,4], [4,3], [1,3], [3,1], [2,4], [4,2]]);
+
+gap> DigraphInsertEdge(D, [1,2], [3,4]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 5 ], [ 2, 4 ], [ 2, 5 ], [ 3, 1 ], [ 3, 6 ], [ 4, 2 ],
+ [ 4, 6 ], [ 5, 1 ], [ 5, 2 ], [ 5, 6 ], [ 6, 3 ], [ 6, 4 ], [ 6, 5 ] ]
+
+# DigraphReduceEdge
+gap> D := DigraphByEdges(IsMutableDigraph, [[1,3], [3,1], [2,4], [4,2], [1,5], [5,1], [2,5], [5,2], [3,6], [6,3], [4,6], [6,4], [5,6], [6,5]]);
+
+gap> DigraphReduceEdge(D, [5,6]);
+
+gap> DigraphEdges(D);
+[ [ 1, 3 ], [ 1, 2 ], [ 2, 4 ], [ 2, 1 ], [ 3, 1 ], [ 3, 4 ], [ 4, 2 ],
+ [ 4, 3 ] ]
+
# Issue #704 SubdigraphsMonomorphisms bug
gap> d := Digraph([[2, 3, 4, 5], [1, 3, 4], [1, 2, 4, 5], [1, 2, 3, 5],
> [1, 3, 4]]);;