Skip to content

Commit d3b5209

Browse files
committed
Refactor tearing code
This refactors tearing to use a clean implementation of the BFGT15 Algorithm N, together with the directed graph abstraction introduced in #1328. I believe the asymptotics of the version I implemented in Graphs.jl are somewhat better than the version we're replacing here from Modia, because the Modia versions caches the level rather than recomputing it (thus potential performing redundant updates) - however, this is probably unlikely to make a difference in practice. That said, this version should also be significantly easier to swap out for one of the more advanced cycle detection algorithms from the literature. Algorithm N is tuned for dense graphs, which our MTK graphs are unlikely to be, so we may want to switch to a sparse cycle detection algorithm in the future.
1 parent 8bec990 commit d3b5209

File tree

4 files changed

+68
-359
lines changed

4 files changed

+68
-359
lines changed

src/bipartite_graph.jl

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -280,50 +280,77 @@ end
280280
struct DiCMOBiGraph
281281
282282
This data structure implements a "directed, contracted, matching-oriented" view of an
283-
original (undirected) bipartite graph. In particular, it performs two largely
284-
orthogonal functions.
283+
original (undirected) bipartite graph. It has two modes, depending on the `Transposed`
284+
flag, which switches the direction of the induced matching.
285285
286-
1. It pairs an undirected bipartite graph with a matching of destination vertex.
286+
Essentially the graph adapter performs two largely orthogonal functions
287+
[`Transposed == true` differences are indicated in square brackets]:
288+
289+
1. It pairs an undirected bipartite graph with a matching of the destination vertex.
287290
288291
This matching is used to induce an orientation on the otherwise undirected graph:
289-
Matched edges pass from destination to source, all other edges pass in the opposite
290-
direction.
292+
Matched edges pass from destination to source [source to desination], all other edges
293+
pass in the opposite direction.
291294
292-
2. It exposes the graph view obtained by contracting the destination vertices into
293-
the source edges.
295+
2. It exposes the graph view obtained by contracting the destination [source] vertices
296+
along the matched edges.
294297
295-
The result of this operation is an induced, directed graph on the source vertices.
298+
The result of this operation is an induced, directed graph on the source [destination] vertices.
296299
The resulting graph has a few desirable properties. In particular, this graph
297300
is acyclic if and only if the induced directed graph on the original bipartite
298301
graph is acyclic.
302+
299303
"""
300-
struct DiCMOBiGraph{I, G<:BipartiteGraph{I}, M} <: Graphs.AbstractGraph{I}
304+
struct DiCMOBiGraph{Transposed, I, G<:BipartiteGraph{I}, M} <: Graphs.AbstractGraph{I}
301305
graph::G
302306
matching::M
307+
DiCMOBiGraph{Transposed}(g::G, m::M) where {Transposed, I, G<:BipartiteGraph{I}, M} =
308+
new{Transposed, I, G, M}(g, m)
303309
end
310+
DiCMOBiGraph{Transposed}(g::BipartiteGraph) where {Transposed} = DiCMOBiGraph{Transposed}(g, Union{Unassigned, Int}[unassigned for i = 1:ndsts(g)])
304311
Graphs.is_directed(::Type{<:DiCMOBiGraph}) = true
305-
Graphs.nv(g::DiCMOBiGraph) = nsrcs(g.graph)
306-
Graphs.vertices(g::DiCMOBiGraph) = 1:nsrcs(g.graph)
312+
Graphs.nv(g::DiCMOBiGraph{Transposed}) where {Transposed} = Transposed ? ndsts(g.graph) : nsrcs(g.graph)
313+
Graphs.vertices(g::DiCMOBiGraph{Transposed}) where {Transposed} = Transposed ? 𝑑vertices(g.graph) : 𝑠vertices(g.graph)
307314

308-
struct CMOOutNeighbors{V}
309-
g::DiCMOBiGraph
315+
struct CMONeighbors{Transposed, V}
316+
g::DiCMOBiGraph{Transposed}
310317
v::V
318+
CMONeighbors{Transposed}(g::DiCMOBiGraph{Transposed}, v::V) where {Transposed, V} =
319+
new{Transposed, V}(g, v)
311320
end
312-
Graphs.outneighbors(g::DiCMOBiGraph, v) = CMOOutNeighbors(g, v)
313-
Base.iterate(c::CMOOutNeighbors) = iterate(c, (c.g.graph.fadjlist[c.v],))
314-
function Base.iterate(c::CMOOutNeighbors, (l, state...))
321+
Graphs.outneighbors(g::DiCMOBiGraph{false}, v) = CMONeighbors{false}(g, v)
322+
Base.iterate(c::CMONeighbors{false}) = iterate(c, (c.g.graph.fadjlist[c.v],))
323+
function Base.iterate(c::CMONeighbors{false}, (l, state...))
315324
while true
316325
r = iterate(l, state...)
317326
r === nothing && return nothing
318327
# If this is a matched edge, skip it, it's reversed in the induced
319328
# directed graph. Otherwise, if there is no matching for this destination
320329
# edge, also skip it, since it got delted in the contraction.
321-
vdst = c.g.matching[r[1]]
322-
if vdst === c.v || vdst === unassigned
330+
vsrc = c.g.matching[r[1]]
331+
if vsrc === c.v || vsrc === unassigned
332+
state = (r[2],)
333+
continue
334+
end
335+
return vsrc, (l, r[2])
336+
end
337+
end
338+
339+
Graphs.inneighbors(g::DiCMOBiGraph{true}, v) = CMONeighbors{true}(g, v)
340+
function Base.iterate(c::CMONeighbors{true})
341+
vsrc = c.g.matching[c.v]
342+
vsrc === unassigned && return nothing
343+
iterate(c, (c.g.graph.fadjlist[vsrc],))
344+
end
345+
function Base.iterate(c::CMONeighbors{true}, (l, state...))
346+
while true
347+
r = iterate(l, state...)
348+
r === nothing && return nothing
349+
if r[1] === c.v
323350
state = (r[2],)
324351
continue
325352
end
326-
return vdst, (l, r[2])
353+
return r[1], (l, r[2])
327354
end
328355
end
329356

0 commit comments

Comments
 (0)