1
1
module BipartiteGraphs
2
2
3
3
export BipartiteEdge, BipartiteGraph, DiCMOBiGraph, Unassigned, unassigned,
4
- Matching
4
+ Matching, ResidualCMOGraph
5
5
6
6
export 𝑠vertices, 𝑑vertices, has_𝑠vertex, has_𝑑vertex, 𝑠neighbors, 𝑑neighbors,
7
7
𝑠edges, 𝑑edges, nsrcs, ndsts, SRC, DST, set_neighbors!, invview,
@@ -19,51 +19,59 @@ struct Unassigned
19
19
const unassigned = Unassigned. instance
20
20
end
21
21
22
- struct Matching{V <: AbstractVector{<:Union{ Unassigned, Int}} } <: AbstractVector{Union{Unassigned , Int}}
22
+ struct Matching{U #= > : Unassigned =# , V <: AbstractVector } <: AbstractVector{Union{U , Int}}
23
23
match:: V
24
24
inv_match:: Union{Nothing, V}
25
25
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 )
29
- Matching (m:: Matching ) = m
26
+ # These constructors work around https://github.com/JuliaLang/julia/issues/41948
27
+ function Matching {V} (m:: Matching ) where {V}
28
+ eltype (m) === Union{V, Int} && return M
29
+ VUT = typeof (similar (m. match, Union{V, Int}))
30
+ Matching {V} (convert (VUT, m. match),
31
+ m. inv_match === nothing ? nothing : convert (VUT, m. inv_match))
32
+ end
33
+ Matching {U} (v:: V ) where {U, V<: AbstractVector } = Matching {U, V} (v, nothing )
34
+ Matching {U} (v:: V , iv:: Union{V, Nothing} ) where {U, V<: AbstractVector } = Matching {U, V} (v, iv)
35
+ Matching (v:: V ) where {U, V<: AbstractVector{Union{U, Int}} } =
36
+ Matching {@isdefined(U) ? U : Unassigned, V} (v, nothing )
37
+ Matching (m:: Int ) = Matching {Unassigned} (Union{Int, Unassigned}[unassigned for _ = 1 : m], nothing )
30
38
31
39
Base. size (m:: Matching ) = Base. size (m. match)
32
40
Base. getindex (m:: Matching , i:: Integer ) = m. match[i]
33
41
Base. iterate (m:: Matching , state... ) = iterate (m. match, state... )
34
42
Base. copy (m:: Matching ) = Matching (copy (m. match), m. inv_match === nothing ? nothing : copy (m. inv_match))
35
- function Base. setindex! (m:: Matching , v:: Union{Integer, Unassigned } , i:: Integer )
43
+ function Base. setindex! (m:: Matching{U} , v:: Union{Integer, U } , i:: Integer ) where {U}
36
44
if m. inv_match != = nothing
37
45
oldv = m. match[i]
38
- oldv != = unassigned && (m. inv_match[oldv] = unassigned)
39
- v != = unassigned && (m. inv_match[v] = i)
46
+ isa ( oldv, Int) && (m. inv_match[oldv] = unassigned)
47
+ isa (v, Int) && (m. inv_match[v] = i)
40
48
end
41
49
return m. match[i] = v
42
50
end
43
51
44
- function Base. push! (m:: Matching , v:: Union{Integer, Unassigned} )
52
+ function Base. push! (m:: Matching{U} , v:: Union{Integer, U} ) where {U}
45
53
push! (m. match, v)
46
54
if v != = unassigned && m. inv_match != = nothing
47
55
m. inv_match[v] = length (m. match)
48
56
end
49
57
end
50
58
51
- function complete (m:: Matching )
59
+ function complete (m:: Matching{U} ) where {U}
52
60
m. inv_match != = nothing && return m
53
- inv_match = Union{Unassigned , Int}[unassigned for _ = 1 : length (m. match)]
61
+ inv_match = Union{U , Int}[unassigned for _ = 1 : length (m. match)]
54
62
for (i, eq) in enumerate (m. match)
55
- eq === unassigned && continue
63
+ isa (eq, Int) || continue
56
64
inv_match[eq] = i
57
65
end
58
- return Matching (collect (m. match), inv_match)
66
+ return Matching {U} (collect (m. match), inv_match)
59
67
end
60
68
61
69
@noinline require_complete (m:: Matching ) =
62
70
m. inv_match === nothing && throw (ArgumentError (" Backwards matching not defined. `complete` the matching first." ))
63
71
64
- function invview (m:: Matching )
72
+ function invview (m:: Matching{U, V} ) where {U, V}
65
73
require_complete (m)
66
- return Matching (m. inv_match, m. match)
74
+ return Matching {U, V} (m. inv_match, m. match)
67
75
end
68
76
69
77
# ##
@@ -355,6 +363,14 @@ The resulting graph has a few desirable properties. In particular, this graph
355
363
is acyclic if and only if the induced directed graph on the original bipartite
356
364
graph is acyclic.
357
365
366
+ # Hypergraph interpretation
367
+
368
+ Consider the bipartite graph `B` as the incidence graph of some hypergraph `H`.
369
+ Note that a maching `M` on `B` in the above sense is equivalent to determining
370
+ an (1,n)-orientation on the hypergraph (i.e. each directed hyperedge has exactly
371
+ one head, but any arbitrary number of tails). In this setting, this is simply
372
+ the graph formed by expanding each directed hyperedge into `n` ordinary edges
373
+ between the same vertices.
358
374
"""
359
375
mutable struct DiCMOBiGraph{Transposed, I, G<: BipartiteGraph{I} , M <: Matching } <: Graphs.AbstractGraph{I}
360
376
graph:: G
@@ -385,8 +401,7 @@ struct CMONeighbors{Transposed, V}
385
401
end
386
402
387
403
Graphs. outneighbors (g:: DiCMOBiGraph{false} , v) = CMONeighbors {false} (g, v)
388
- Graphs. inneighbors (g:: DiCMOBiGraph{false} , v) = CMONeighbors {true} (invview (g), v)
389
- Graphs. all_neighbors (g:: DiCMOBiGraph{true} , v:: Integer ) = 𝑠neighbors (g. graph, v)
404
+ Graphs. inneighbors (g:: DiCMOBiGraph{false} , v) = inneighbors (invview (g), v)
390
405
Base. iterate (c:: CMONeighbors{false} ) = iterate (c, (c. g. graph. fadjlist[c. v],))
391
406
function Base. iterate (c:: CMONeighbors{false} , (l, state... ))
392
407
while true
@@ -405,15 +420,15 @@ function Base.iterate(c::CMONeighbors{false}, (l, state...))
405
420
end
406
421
Base. length (c:: CMONeighbors{false} ) = count (_-> true , c)
407
422
408
- lift (f, x) = (x === unassigned || isnothing (x)) ? nothing : f (x)
423
+ liftint (f, x) = (! isa (x, Int)) ? nothing : f (x)
424
+ liftnothing (f, x) = x === nothing ? nothing : f (x)
409
425
410
426
_vsrc (c:: CMONeighbors{true} ) = c. g. matching[c. v]
411
- _neighbors (c:: CMONeighbors{true} ) = lift (vsrc-> c. g. graph. fadjlist[vsrc], _vsrc (c))
412
- Base. length (c:: CMONeighbors{true} ) = something (lift (length, _neighbors (c)), 1 ) - 1
427
+ _neighbors (c:: CMONeighbors{true} ) = liftint (vsrc-> c. g. graph. fadjlist[vsrc], _vsrc (c))
428
+ Base. length (c:: CMONeighbors{true} ) = something (liftnothing (length, _neighbors (c)), 1 ) - 1
413
429
Graphs. inneighbors (g:: DiCMOBiGraph{true} , v) = CMONeighbors {true} (g, v)
414
- Graphs. outneighbors (g:: DiCMOBiGraph{true} , v) = CMONeighbors {false} (invview (g), v)
415
- Graphs. all_neighbors (g:: DiCMOBiGraph{true} , v:: Integer ) = 𝑑neighbors (g. graph, v)
416
- Base. iterate (c:: CMONeighbors{true} ) = lift (ns-> iterate (c, (ns,)), _neighbors (c))
430
+ Graphs. outneighbors (g:: DiCMOBiGraph{true} , v) = outneighbors (invview (g), v)
431
+ Base. iterate (c:: CMONeighbors{true} ) = liftnothing (ns-> iterate (c, (ns,)), _neighbors (c))
417
432
function Base. iterate (c:: CMONeighbors{true} , (l, state... ))
418
433
while true
419
434
r = iterate (l, state... )
442
457
Graphs. has_edge (g:: DiCMOBiGraph{true} , a, b) = a in inneighbors (g, b)
443
458
Graphs. has_edge (g:: DiCMOBiGraph{false} , a, b) = b in outneighbors (g, a)
444
459
460
+ """
461
+ struct ResidualCMOGraph
462
+
463
+ For a bipartite graph and matching on the graph's destination vertices, this
464
+ wrapper exposes the induced graph on the destination vertices formed by those
465
+ destination and source vertices that are left unmatched. In particular, two
466
+ (destination) vertices a and b are neighbors if they are both unassigned and
467
+ there is some unassigned source vertex `s` such that `s` is a neighbor (in the
468
+ bipartite graph) of both `a` and `b`.
469
+
470
+ # Hypergraph interpreation
471
+
472
+ Refer to the hypergraph interpretation of the DiCMOBiGraph. Now consider the
473
+ hypergraph left over after removing all edges that are oriented by the mapping.
474
+ This graph is the undirected graph obtained by replacing all hyper edges by the
475
+ maximal undirected graph on the vertices that are members of the original hyper
476
+ edge.
477
+
478
+ # Nota Bene
479
+
480
+ 1. For technical reasons, the `vertices` function includes even those vertices
481
+ vertices that are assigned in the original hypergraph, even though they
482
+ are conceptually not part of the graph.
483
+ 2. This graph is not strict. In particular, multi edges between vertices are
484
+ allowed and common.
485
+ """
486
+ struct ResidualCMOGraph{I, G<: BipartiteGraph{I} , M <: Matching } <: Graphs.AbstractGraph{I}
487
+ graph:: G
488
+ matching:: M
489
+ function ResidualCMOGraph {I, G, M} (g:: G , m:: M ) where {I, G<: BipartiteGraph{I} , M}
490
+ require_complete (g)
491
+ require_complete (m)
492
+ new {I, G, M} (g, m)
493
+ end
494
+ end
495
+ ResidualCMOGraph (g:: G , m:: M ) where {I, G<: BipartiteGraph{I} , M} = ResidualCMOGraph {I, G, M} (g, m)
496
+
497
+ invview (rcg:: ResidualCMOGraph ) = ResidualCMOGraph (invview (rcg. graph), invview (rcg. matching))
498
+
499
+ Graphs. is_directed (:: Type{<:ResidualCMOGraph} ) = false
500
+ Graphs. nv (rcg:: ResidualCMOGraph ) = ndsts (rcg. graph)
501
+ Graphs. vertices (rcg:: ResidualCMOGraph ) = 𝑑vertices (rcg. graph)
502
+ function Graphs. neighbors (rcg:: ResidualCMOGraph , v:: Integer )
503
+ rcg. matching[v] != = unassigned && return ()
504
+ Iterators. filter (
505
+ vdst-> rcg. matching[vdst] === unassigned,
506
+ Iterators. flatten (rcg. graph. fadjlist[vsrc] for
507
+ vsrc in rcg. graph. badjlist[v] if
508
+ invview (rcg. matching)[vsrc] === unassigned))
509
+ end
510
+
445
511
end # module
0 commit comments