1
1
module BipartiteGraphs
2
2
3
3
export BipartiteEdge, BipartiteGraph, DiCMOBiGraph, Unassigned, unassigned,
4
- Matching
4
+ Matching, ResidualCMOGraph, InducedCondensationGraph, maximal_matching,
5
+ construct_augmenting_path!
5
6
6
7
export 𝑠vertices, 𝑑vertices, has_𝑠vertex, has_𝑑vertex, 𝑠neighbors, 𝑑neighbors,
7
8
𝑠edges, 𝑑edges, nsrcs, ndsts, SRC, DST, set_neighbors!, invview,
@@ -18,46 +19,69 @@ struct Unassigned
18
19
global unassigned
19
20
const unassigned = Unassigned. instance
20
21
end
22
+ # Behaves as a scalar
23
+ Base. length (u:: Unassigned ) = 1
24
+ Base. size (u:: Unassigned ) = ()
25
+ Base. iterate (u:: Unassigned ) = (unassigned, nothing )
26
+ Base. iterate (u:: Unassigned , state) = nothing
21
27
22
- struct Matching{V<: AbstractVector{<:Union{Unassigned, Int}} } <: AbstractVector{Union{Unassigned, Int}}
28
+ Base. show (io:: IO , :: Unassigned ) =
29
+ printstyled (io, " u" ; color= :light_black )
30
+
31
+ struct Matching{U #= > :Unassigned =# , V<: AbstractVector } <: AbstractVector{Union{U, Int}}
23
32
match:: V
24
33
inv_match:: Union{Nothing, V}
25
34
end
26
- Matching (v:: V ) where {V<: AbstractVector{<:Union{Unassigned, Int}} } =
27
- Matching {V} (v, nothing )
28
- Matching (m:: Int ) = Matching (Union{Int, Unassigned}[unassigned for _ = 1 : m], nothing )
35
+ # These constructors work around https://github.com/JuliaLang/julia/issues/41948
36
+ function Matching {V} (m:: Matching ) where {V}
37
+ eltype (m) === Union{V, Int} && return M
38
+ VUT = typeof (similar (m. match, Union{V, Int}))
39
+ Matching {V} (convert (VUT, m. match),
40
+ m. inv_match === nothing ? nothing : convert (VUT, m. inv_match))
41
+ end
29
42
Matching (m:: Matching ) = m
43
+ Matching {U} (v:: V ) where {U, V<: AbstractVector } = Matching {U, V} (v, nothing )
44
+ Matching {U} (v:: V , iv:: Union{V, Nothing} ) where {U, V<: AbstractVector } = Matching {U, V} (v, iv)
45
+ Matching (v:: V ) where {U, V<: AbstractVector{Union{U, Int}} } =
46
+ Matching {@isdefined(U) ? U : Unassigned, V} (v, nothing )
47
+ Matching (m:: Int ) = Matching {Unassigned} (Union{Int, Unassigned}[unassigned for _ = 1 : m], nothing )
30
48
31
49
Base. size (m:: Matching ) = Base. size (m. match)
32
50
Base. getindex (m:: Matching , i:: Integer ) = m. match[i]
33
51
Base. iterate (m:: Matching , state... ) = iterate (m. match, state... )
34
- function Base. setindex! (m:: Matching , v:: Integer , i:: Integer )
52
+ Base. copy (m:: Matching ) = Matching (copy (m. match), m. inv_match === nothing ? nothing : copy (m. inv_match))
53
+ function Base. setindex! (m:: Matching{U} , v:: Union{Integer, U} , i:: Integer ) where {U}
35
54
if m. inv_match != = nothing
36
- m. inv_match[v] = i
55
+ oldv = m. match[i]
56
+ isa (oldv, Int) && (m. inv_match[oldv] = unassigned)
57
+ isa (v, Int) && (m. inv_match[v] = i)
37
58
end
38
59
return m. match[i] = v
39
60
end
40
61
41
- function Base. push! (m:: Matching , v:: Union{Integer, Unassigned} )
62
+ function Base. push! (m:: Matching{U} , v:: Union{Integer, U} ) where {U}
42
63
push! (m. match, v)
43
64
if v != = unassigned && m. inv_match != = nothing
44
65
m. inv_match[v] = length (m. match)
45
66
end
46
67
end
47
68
48
- function complete (m:: Matching )
69
+ function complete (m:: Matching{U} ) where {U}
49
70
m. inv_match != = nothing && return m
50
- inv_match = Union{Unassigned , Int}[unassigned for _ = 1 : length (m. match)]
71
+ inv_match = Union{U , Int}[unassigned for _ = 1 : length (m. match)]
51
72
for (i, eq) in enumerate (m. match)
52
- eq === unassigned && continue
73
+ isa (eq, Int) || continue
53
74
inv_match[eq] = i
54
75
end
55
- return Matching (collect (m. match), inv_match)
76
+ return Matching {U} (collect (m. match), inv_match)
56
77
end
57
78
58
- function invview (m:: Matching )
79
+ @noinline require_complete (m:: Matching ) =
59
80
m. inv_match === nothing && throw (ArgumentError (" Backwards matching not defined. `complete` the matching first." ))
60
- return Matching (m. inv_match, m. match)
81
+
82
+ function invview (m:: Matching{U, V} ) where {U, V}
83
+ require_complete (m)
84
+ return Matching {U, V} (m. inv_match, m. match)
61
85
end
62
86
63
87
# ##
@@ -121,6 +145,26 @@ mutable struct BipartiteGraph{I<:Integer, M} <: Graphs.AbstractGraph{I}
121
145
metadata:: M
122
146
end
123
147
BipartiteGraph (ne:: Integer , fadj:: AbstractVector , badj:: Union{AbstractVector,Integer} = maximum (maximum, fadj); metadata= nothing ) = BipartiteGraph (ne, fadj, badj, metadata)
148
+ BipartiteGraph (fadj:: AbstractVector , badj:: Union{AbstractVector,Integer} = maximum (maximum, fadj); metadata= nothing ) =
149
+ BipartiteGraph (mapreduce (length, + , fadj; init= 0 ), fadj, badj, metadata)
150
+
151
+ @noinline require_complete (g:: BipartiteGraph ) = g. badjlist isa AbstractVector || throw (ArgumentError (" The graph has no back edges. Use `complete`." ))
152
+
153
+ function invview (g:: BipartiteGraph )
154
+ require_complete (g)
155
+ BipartiteGraph (g. ne, g. badjlist, g. fadjlist)
156
+ end
157
+
158
+ function complete (g:: BipartiteGraph{I} ) where {I}
159
+ isa (g. badjlist, AbstractVector) && return g
160
+ badjlist = Vector{I}[Vector {I} () for _ in 1 : g. badjlist]
161
+ for (s, l) in enumerate (g. fadjlist)
162
+ for d in l
163
+ push! (badjlist[d], s)
164
+ end
165
+ end
166
+ BipartiteGraph (g. ne, g. fadjlist, badjlist)
167
+ end
124
168
125
169
"""
126
170
```julia
@@ -147,6 +191,7 @@ function BipartiteGraph(nsrcs::T, ndsts::T, backedge::Val{B}=Val(true); metadata
147
191
BipartiteGraph (0 , fadjlist, badjlist, metadata)
148
192
end
149
193
194
+ Base. copy (bg:: BipartiteGraph ) = BipartiteGraph (bg. ne, copy (bg. fadjlist), copy (bg. badjlist), deepcopy (bg. metadata))
150
195
Base. eltype (:: Type{<:BipartiteGraph{I}} ) where I = I
151
196
function Base. empty! (g:: BipartiteGraph )
152
197
foreach (empty!, g. fadjlist)
@@ -159,8 +204,6 @@ function Base.empty!(g::BipartiteGraph)
159
204
end
160
205
Base. length (:: BipartiteGraph ) = error (" length is not well defined! Use `ne` or `nv`." )
161
206
162
- @noinline throw_no_back_edges () = throw (ArgumentError (" The graph has no back edges." ))
163
-
164
207
if isdefined (Graphs, :has_contiguous_vertices )
165
208
Graphs. has_contiguous_vertices (:: Type{<:BipartiteGraph} ) = false
166
209
end
@@ -172,7 +215,7 @@ has_𝑠vertex(g::BipartiteGraph, v::Integer) = v in 𝑠vertices(g)
172
215
has_𝑑vertex (g:: BipartiteGraph , v:: Integer ) = v in 𝑑vertices (g)
173
216
𝑠neighbors (g:: BipartiteGraph , i:: Integer , with_metadata:: Val{M} = Val (false )) where M = M ? zip (g. fadjlist[i], g. metadata[i]) : g. fadjlist[i]
174
217
function 𝑑neighbors (g:: BipartiteGraph , j:: Integer , with_metadata:: Val{M} = Val (false )) where M
175
- g . badjlist isa AbstractVector || throw_no_back_edges ( )
218
+ require_complete (g )
176
219
M ? zip (g. badjlist[j], (g. metadata[i][j] for i in g. badjlist[j])) : g. badjlist[j]
177
220
end
178
221
Graphs. ne (g:: BipartiteGraph ) = g. ne
@@ -185,7 +228,53 @@ ndsts(g::BipartiteGraph) = length(𝑑vertices(g))
185
228
function Graphs. has_edge (g:: BipartiteGraph , edge:: BipartiteEdge )
186
229
@unpack src, dst = edge
187
230
(src in 𝑠vertices (g) && dst in 𝑑vertices (g)) || return false # edge out of bounds
188
- insorted (𝑠neighbors (src), dst)
231
+ insorted (dst, 𝑠neighbors (g, src))
232
+ end
233
+ Base. in (edge:: BipartiteEdge , g:: BipartiteGraph ) = Graphs. has_edge (g, edge)
234
+
235
+ # ## Maximal matching
236
+ """
237
+ construct_augmenting_path!(m::Matching, g::BipartiteGraph, vsrc, dstfilter, vcolor=falses(ndsts(g)), ecolor=falses(nsrcs(g))) -> path_found::Bool
238
+
239
+ Try to construct an augmenting path in matching and if such a path is found,
240
+ update the matching accordingly.
241
+ """
242
+ function construct_augmenting_path! (matching:: Matching , g:: BipartiteGraph , vsrc, dstfilter, dcolor= falses (ndsts (g)), scolor= falses (nsrcs (g)))
243
+ scolor[vsrc] = true
244
+
245
+ # if a `vdst` is unassigned and the edge `vsrc <=> vdst` exists
246
+ for vdst in 𝑠neighbors (g, vsrc)
247
+ if dstfilter (vdst) && matching[vdst] === unassigned
248
+ matching[vdst] = vsrc
249
+ return true
250
+ end
251
+ end
252
+
253
+ # for every `vsrc` such that edge `vsrc <=> vdst` exists and `vdst` is uncolored
254
+ for vdst in 𝑠neighbors (g, vsrc)
255
+ (dstfilter (vdst) && ! dcolor[vdst]) || continue
256
+ dcolor[vdst] = true
257
+ if construct_augmenting_path! (matching, g, matching[vdst], dstfilter, dcolor, scolor)
258
+ matching[vdst] = vsrc
259
+ return true
260
+ end
261
+ end
262
+ return false
263
+ end
264
+
265
+ """
266
+ maximal_matching(g::BipartiteGraph, [srcfilter], [dstfilter])
267
+
268
+ For a bipartite graph `g`, construct a maximal matching of destination to source
269
+ vertices, subject to the constraint that vertices for which `srcfilter` or `dstfilter`,
270
+ return `false` may not be matched.
271
+ """
272
+ function maximal_matching (g:: BipartiteGraph , srcfilter= vsrc-> true , dstfilter= vdst-> true )
273
+ matching = Matching (ndsts (g))
274
+ foreach (Iterators. filter (srcfilter, 𝑠vertices (g))) do vsrc
275
+ construct_augmenting_path! (matching, g, vsrc, dstfilter)
276
+ end
277
+ return matching
189
278
end
190
279
191
280
# ##
@@ -333,6 +422,14 @@ The resulting graph has a few desirable properties. In particular, this graph
333
422
is acyclic if and only if the induced directed graph on the original bipartite
334
423
graph is acyclic.
335
424
425
+ # Hypergraph interpretation
426
+
427
+ Consider the bipartite graph `B` as the incidence graph of some hypergraph `H`.
428
+ Note that a maching `M` on `B` in the above sense is equivalent to determining
429
+ an (1,n)-orientation on the hypergraph (i.e. each directed hyperedge has exactly
430
+ one head, but any arbitrary number of tails). In this setting, this is simply
431
+ the graph formed by expanding each directed hyperedge into `n` ordinary edges
432
+ between the same vertices.
336
433
"""
337
434
mutable struct DiCMOBiGraph{Transposed, I, G<: BipartiteGraph{I} , M <: Matching } <: Graphs.AbstractGraph{I}
338
435
graph:: G
@@ -348,6 +445,9 @@ function DiCMOBiGraph{Transposed}(g::BipartiteGraph, m::M) where {Transposed, M}
348
445
DiCMOBiGraph {Transposed} (g, missing , m)
349
446
end
350
447
448
+ invview (g:: DiCMOBiGraph{Transposed} ) where {Transposed} =
449
+ DiCMOBiGraph {!Transposed} (invview (g. graph), g. ne, invview (g. matching))
450
+
351
451
Graphs. is_directed (:: Type{<:DiCMOBiGraph} ) = true
352
452
Graphs. nv (g:: DiCMOBiGraph{Transposed} ) where {Transposed} = Transposed ? ndsts (g. graph) : nsrcs (g. graph)
353
453
Graphs. vertices (g:: DiCMOBiGraph{Transposed} ) where {Transposed} = Transposed ? 𝑑vertices (g. graph) : 𝑠vertices (g. graph)
@@ -360,6 +460,7 @@ struct CMONeighbors{Transposed, V}
360
460
end
361
461
362
462
Graphs. outneighbors (g:: DiCMOBiGraph{false} , v) = CMONeighbors {false} (g, v)
463
+ Graphs. inneighbors (g:: DiCMOBiGraph{false} , v) = inneighbors (invview (g), v)
363
464
Base. iterate (c:: CMONeighbors{false} ) = iterate (c, (c. g. graph. fadjlist[c. v],))
364
465
function Base. iterate (c:: CMONeighbors{false} , (l, state... ))
365
466
while true
@@ -376,14 +477,17 @@ function Base.iterate(c::CMONeighbors{false}, (l, state...))
376
477
return vsrc, (l, r[2 ])
377
478
end
378
479
end
480
+ Base. length (c:: CMONeighbors{false} ) = count (_-> true , c)
379
481
380
- lift (f, x) = (x === unassigned || isnothing (x)) ? nothing : f (x)
482
+ liftint (f, x) = (! isa (x, Int)) ? nothing : f (x)
483
+ liftnothing (f, x) = x === nothing ? nothing : f (x)
381
484
382
485
_vsrc (c:: CMONeighbors{true} ) = c. g. matching[c. v]
383
- _neighbors (c:: CMONeighbors{true} ) = lift (vsrc-> c. g. graph. fadjlist[vsrc], _vsrc (c))
384
- Base. length (c:: CMONeighbors{true} ) = something (lift (length, _neighbors (c)), 1 ) - 1
486
+ _neighbors (c:: CMONeighbors{true} ) = liftint (vsrc-> c. g. graph. fadjlist[vsrc], _vsrc (c))
487
+ Base. length (c:: CMONeighbors{true} ) = something (liftnothing (length, _neighbors (c)), 1 ) - 1
385
488
Graphs. inneighbors (g:: DiCMOBiGraph{true} , v) = CMONeighbors {true} (g, v)
386
- Base. iterate (c:: CMONeighbors{true} ) = lift (ns-> iterate (c, (ns,)), _neighbors (c))
489
+ Graphs. outneighbors (g:: DiCMOBiGraph{true} , v) = outneighbors (invview (g), v)
490
+ Base. iterate (c:: CMONeighbors{true} ) = liftnothing (ns-> iterate (c, (ns,)), _neighbors (c))
387
491
function Base. iterate (c:: CMONeighbors{true} , (l, state... ))
388
492
while true
389
493
r = iterate (l, state... )
@@ -396,16 +500,15 @@ function Base.iterate(c::CMONeighbors{true}, (l, state...))
396
500
end
397
501
end
398
502
503
+
399
504
_edges (g:: DiCMOBiGraph{Transposed} ) where Transposed = Transposed ?
400
505
((w=> v for w in inneighbors (g, v)) for v in vertices (g)) :
401
506
((v=> w for w in outneighbors (g, v)) for v in vertices (g))
402
- _count (c:: CMONeighbors{true} ) = length (c)
403
- _count (c:: CMONeighbors{false} ) = count (_-> true , c)
404
507
405
508
Graphs. edges (g:: DiCMOBiGraph ) = (Graphs. SimpleEdge (p) for p in Iterators. flatten (_edges (g)))
406
509
function Graphs. ne (g:: DiCMOBiGraph )
407
510
if g. ne === missing
408
- g. ne = mapreduce (x-> _count (x. iter), + , _edges (g))
511
+ g. ne = mapreduce (x-> length (x. iter), + , _edges (g))
409
512
end
410
513
return g. ne
411
514
end
0 commit comments