I am interested in finding the positions of "chains" (sequences of vertex degree 2 vertices) in a graph. I plan on collapsing these chains later to vertices with labels indicating that they are collapsed chains.
For example, I would like the following input-output.
Input:
Graph[{1 \[DirectedEdge] 2, 2 \[DirectedEdge] 3,
3 \[DirectedEdge] 4, 4 \[DirectedEdge] 5, 6 \[DirectedEdge] 7,
7 \[DirectedEdge] 8, 5 \[DirectedEdge] 8, 8 \[DirectedEdge] 9}]
Output:
{"Chain1" -> Graph[{1 \[DirectedEdge] 2, 2 \[DirectedEdge] 3,
3 \[DirectedEdge] 4, 4 \[DirectedEdge] 5, 5 \[DirectedEdge]
6}],
"Chain2" -> Graph[{1 \[DirectedEdge] 2, 2 \[DirectedEdge] 3}],
HighlightGraph[
Graph[{Labeled[1, "Chain1"], Labeled[3, "Chain2"]}, {1 -> 2, 3
-> 2,
2 -> 4}], {1, 3}]}
Why do you want to do that ?
I want to make a code that creates a symbolic expression for the output of an artificial neural network (I am interested if anyone has a code that already does that).
I already made a code that works with simple feedforward networks (NetChain) that contain only ElementwiseLayer and LinearLayer.
I would like to use my code for NetChains to replace any chain subgraph in NetGraph with its symbolic functional equivalent and then use these intermediate expressions to compute the full symbolic expression of a network. A typical example would be to replace a network that adds the results of two NetChains by first finding the expressions for the NetChains and then simply adding them.
Edit 1
In the output example given I would like the two first elements in Chain1 and Chain2 to retain the names of the vertices. The length of the chain is not enough for my purpose.
Edit 2
Procedure incorporating the methods below
Note: There are 2 conventions, the last one using @Szabolcs's answer is currently incomplete for the problem at hand but still provides a solution for many cases.
The answers from @Szabolcs and @lericr offer different solutions on how to collapse a chain within a graph. Should the chain be collapsed to a vertex or an edge? Which vertex should we collapse the chain to? The first, the last? To clarify what I mean we may look at the following input-output examples.
Input:
Graph[{1 -> 2, 2 -> 3, 3 -> 4, 5 -> 4, 4 -> 8, 8 -> 9, 9 -> 10,
10 -> 11, 11 -> 12, 12 -> 13, 13 -> 14, 11 -> 15, 15 -> 16,
15 -> 17}, ImageSize -> Small, VertexLabels -> Automatic]
Output using the method from @lericr:
Output using the method from @Szabolcs:
/////////////////
Discussion on the differences between the outputs:
Notice that in the first output vertex 8 represents the collapsed chain whereas in the second output the collapsed chain is represented by the edge 4->11. Notice as well that in the first output, the vertex that represents a chain is always the first element in the chain (with respect to the graph arrow flow). This might be natural for a directed graph but might be difficult to work with when using undirected graphs. Indeed, in the first output graph, vertex 1 is far from vertex 4 in the original graph whereas vertex 12 is close to vertex 11. This could lead to inconsistencies in some applications while being useful when working with directed graphs in others. The second output graph is perhaps easier to work with in general. Moreover, @Szabolcs one function solution might be easier to use in most graph-chain related problems which is why I accepted that user's solution.
//////////////////
In the following, I will use the answers below to provide a solution for both conventions. First, however, I provide below the code for obtaining the chains which is used in both cases.
Following the suggestion of @Szabolcs, the list of collapsed chains can be obtained using**(see footnote):
chains=WeaklyConnectedGraphComponents[Subgraph[inputgraph, _?
(VertexDegree[testGraph2, #] < 3 &)] // EdgeList]
Output:
The slightly longer method used by @lericr might require less computer time since it does not use patterns but I have not checked.
///////////////
** footnote : The list chains is equivalent to @lericr's chainSubgraphs I believe. Also, EdgeList was used to remove any lone wolf vertices from the disconnected graphs. Equivalently one could also use VertexDelete as in @lericr's answer:
WeaklyConnectedGraphComponents[
VertexDelete[inputgraph, _?(VertexDegree[inputgraph, #] > 2 &)]
// EdgeList]
This VertexDelete code has about the same speed as the Subgraph one when using the input examples above.
Vertex collapsed-chain convention
I would like to mention that @lericr's answer is fairly simple and uses only Mathematica built-in functions whereas the @Szabolcs method requires installing a package (the page provided in @Szabolcs answer makes the installation a very easy copy-paste-enter using Mathematica).
In the vertex convention, the collapsed chains in the output graph are identified by the first vertex in each chain. These may be extracted using:
chainNodes=TreeData /@ GraphTree /@ chains
Or the method used by @Ben Izd:
chainNodes=Last /@ (VertexInComponent[#, Last@VertexList[#]] &)
/@ chains
which is a bit longer to write and more difficult to understand and remember but it was about 200 times faster in my tests involving chains containing over a hundred vertices. Of course, this remark is important only for graphs containing many long chains.
The outputgraph is obtained easily using
VertexContract[inputgraph,chains]
Then we may use @Ben Izd's answer where I included the option to highlight the collapsed chains:
labels = Array["Chain" ~~ ToString[#] &, Length @ chainNodes];
AnnotationValue[{outputgraph, chainNodes}, VertexLabels] = labels;
Append[MapThread[Rule, {labels, chains}], HighlightGraph[Outputgraph, chainNodes]]
which leads to the desired result:
Edge collapsed-chain convention
Note: currently incomplete but provides a solution for many cases.
The method provided by @Szabolcs requires installing a package but the page provided in @Szabolcs answer makes the installation a very easy copy-paste-enter using Mathematica.
The output graph does not require computing the chains and is thus a much quicker solution when full knowledge of the chain content is not required.
If IGraph/M is not installed, please check @Szabolcs's answer.
The output graph is obtained using :
outputgraph=IGSmoothen[inputgraph]
If one only wishes to identify which edges correspond to collapsed chains, independently of which collapsed edge corresponds to which edge, then it is sufficient to use
outputgraph=IGSmoothen[inputgraph,EdgeLabels -> "EdgeWeight"]
where the edge weight of a collapsed chain is equal to the total number of edges**(see footnote). If the user wishes to have a more visual representation where the edges corresponding to collapsed chains are highlighted then one may use:
HighlightGraph[outputgraph,
GraphDifference[outputgraph,inputgraph] //EdgeList]
If the user needs to identify which edge corresponds to which chain then further work is needed.
////////////////
Discussion on the convention for identifying collapsed chain edges with their chains
One may identify collapsed-chain edges with the original chains by looking at the last, or outflowing, vertex of a collapsed chain (for example vertex 11 in the above output example). These vertices are not vertices of the chains but correspond to the next, or outflowing, a vertex that comes right after the last vertex of a chain in the original input graph (for example 11 in the collapsed chain comes after 10 which is the last vertex in the chain 8->9->10). The last vertex rather than the first vertex is needed due to problems at the boundary of a graph (see for example the chain 1->2->3). Hence a natural identification, or association, can be made between the outflowing vertex of a collapsed chain and the chain it represents. This should be straightforward to implement but I do not have much time currently.
/////////////////////
////////////////////
**footnote: See @Szabolcs's answer for an image showing that the weights are also numerical (like 4. instead of 4)).
IGSmoothen
which does exactly this: it contracts degree-2 vertices. $\endgroup$VertexInComponent
which is applied directly on the graph without converting to other types. $\endgroup$