Update: A more convenient way than the original post is to generate a seperate graph with only the new edges and combine the GraphicsGroupBox
es of the two graphs:
ClearAll[graphAddF]
graphAddF = RawBoxes[With[{gg2 = Cases[ToBoxes[#2],
GraphicsGroupBox[x_] :> x[[1]], {0, Infinity}][[1]]},
Replace[ToBoxes[#], GraphicsGroupBox[{x_, y_}] :>
GraphicsGroupBox[{{x, gg2}, y}], {0, Infinity}]]] &;
Examples:
g = Graph[{1 -> 2, 2 -> 3, 1 -> 4, 4 -> 3, 4 -> 2, 4 -> 5},
EdgeStyle -> Arrowheads[{{Medium, 0.5}}], BaseStyle -> Thick,
VertexLabels -> "Name", GraphLayout -> "LayeredDigraphEmbedding",
ImagePadding -> 10, ImageSize -> 300];
{newedgesa, newedgesb} = {{1 -> 3, 5 -> 1}, {1 -> 3, 5 -> 1, 3 -> 5}};
{curvaturesa, curvaturesb} = {{-0.5, 0}, {-0.5, 0, 0}};
g2 = Graph[newedgesa, VertexCoordinates -> GraphEmbedding[g][[{1, 3, 5}]],
ImageSize -> 200, BaseStyle -> {Thick, Red},
EdgeShapeFunction -> Thread[newedgesa -> (curvedArcF[{{Large, .75}}] /@ curvaturesa)]];
g3 = Graph[newedgesb, VertexCoordinates -> GraphEmbedding[g][[{1, 3, 5}]],
ImageSize -> 200, BaseStyle -> {Thick, Red},
EdgeShapeFunction -> Thread[newedgesb -> (curvedArcF[{{Large, .75}}] /@ curvaturesb)]];
Row[{g2, graphAddF[g, g2], g3, graphAddF[g, g3]}]

Original post:
A work-around until someone posts a more direct answer on the inner workings of edge routing in various embeddings:
to keep the existing vertex and edge locations untouched
We can construct EdgeShapeFunction
s extracting the edge primitives from the box expression of a graph:
ClearAll[edgeRoutesF, curvedArcF]
edgeRoutesF[g_Graph] := Module[{grgrp = Cases[ToBoxes[g],
GraphicsGroupBox[x_] :> (x[[1]] /. {dirs___, sb : StyleBox[_, __] ..} :>
{StyleBox[#, ## & @@Flatten[{dirs, #2}]] & @@@ {sb}} /.
{DynamicLocation[v1_, ___], mid___, DynamicLocation[v2_, ___]} :> {v1, mid, v2}),
{0, Infinity}][[1, 1]], edges},
edges = Cases[grgrp, {v1_String, mid___, v2_String} :>
(DirectedEdge @@ (ToExpression /@ StringSplit[{v1, v2}, "$"][[All, -1]])),
{0, Infinity}];
Thread[edges -> (Function /@ (grgrp /. {v1_String, mid___, v2_String} :>
{#[[1]], mid, #[[2]]}))]]
To construct BezierCurve
s for the newly added edges we can use the built-in (but undocumented) EdgeShapeFunction
"CurvedArc"
:
curvedArcF[ah_: {{Medium, .5}}][curv_: .5] := Composition[Style[#, Arrowheads[ah]] &,
Arrow, GraphElementData[{"CurvedArc", "Curvature" -> curv}]]
Examples:
g = Graph[{1 -> 2, 2 -> 3, 1 -> 4, 4 -> 3, 4 -> 2, 4 -> 5},
EdgeStyle -> Arrowheads[{{Medium, 0.5}}], VertexLabels -> "Name",
GraphLayout -> "LayeredDigraphEmbedding", ImagePadding -> 10, ImageSize -> 300];
newedgesa = DirectedEdge @@@ {{1, 3}, {5, 1}};
curvaturesa = {-.5, -1.5};
edgeshapesa = Join[edgeRoutesF@g,
Thread[newedgesa -> (curvedArcF[{{Large, .75}}] /@ curvaturesa)]];
ga = SetProperty[EdgeAdd[g, newedgesa],
{EdgeShapeFunction -> edgeshapesa, VertexCoordinates -> GraphEmbedding[g],
ImageSize -> 400}];
newedgesb = Join[newedgesa, {DirectedEdge[3, 5]}];
curvaturesb = Join[curvaturesa, {0}];
edgeshapesb = Join[edgeRoutesF@g,
Thread[newedgesb -> (curvedArcF[{{Large, .75}}] /@ curvaturesb)]];
gb = SetProperty[EdgeAdd[g, newedgesb],
{EdgeShapeFunction -> edgeshapesb, VertexCoordinates -> GraphEmbedding[g],
ImageSize -> 400}];
Row[{g, ga, gb}]

With GraphLayout -> "SpringElectricalEmbedding"
and curvaturesa = {1.5, .5}
we get
Row[{g, ga, gb}]
