From 9a4ec5c124048c2caf70eced5fc5fcd4eb635efe Mon Sep 17 00:00:00 2001
From: etienneINSA <etienne.mace_de_gastines@insa-rouen.fr>
Date: Thu, 2 May 2024 16:28:48 +0200
Subject: [PATCH] add accessors

---
 src/GraphsMatching.jl |  50 +++-
 test/runtests.jl      | 535 +++++++++++++++++++++++-------------------
 2 files changed, 338 insertions(+), 247 deletions(-)

diff --git a/src/GraphsMatching.jl b/src/GraphsMatching.jl
index 81e35df..f0e261f 100644
--- a/src/GraphsMatching.jl
+++ b/src/GraphsMatching.jl
@@ -7,10 +7,20 @@ using SparseArrays: spzeros
 using JuMP
 using MathOptInterface
 const MOI = MathOptInterface
-import BlossomV # 'using BlossomV'  leads to naming conflicts with JuMP
+using BlossomV: BlossomV # 'using BlossomV'  leads to naming conflicts with JuMP
 using Hungarian
 
-export MatchingResult, maximum_weight_matching, maximum_weight_maximal_matching, minimum_weight_perfect_matching, HungarianAlgorithm, LPAlgorithm
+export MatchingResult,
+    weight,
+    is_matched_vertex,
+    matching_vertex,
+    matching_vertices,
+    matched_edges,
+    maximum_weight_matching,
+    maximum_weight_maximal_matching,
+    minimum_weight_perfect_matching,
+    HungarianAlgorithm,
+    LPAlgorithm
 
 """
     struct MatchingResult{U}
@@ -30,6 +40,42 @@ struct MatchingResult{U<:Real}
     mate::Vector{Int}
 end
 
+"""
+    weight(m::MatchingResult)
+
+    returns the total weight of the matching
+"""
+weight(m::MatchingResult) = m.weight
+
+"""
+    is_matched_vertex(m::MatchingResult, u)
+
+    returns true if vertex `u` is matched with another vertex
+"""
+is_matched_vertex(m::MatchingResult, u) = (m.mate[u] != -1)
+
+"""
+    matching_vertex(m::MatchingResult, u)
+
+    returns the vertex matched with `u` (-1 if `u` is not matched)
+"""
+matching_vertex(m::MatchingResult, u) = m.mate[u]
+
+"""
+    matching_vertices(m::MatchingResult)
+
+    returns a list of the matching vertices (-1 if the vertex is not matched)
+"""
+matching_vertices(m::MatchingResult) = m.mate
+
+"""
+    matching_vertex(m::MatchingResult, u)
+
+    returns a list of the matched edges
+"""
+matched_edges(m::MatchingResult) =
+    [Edge(u, v) for (u, v) in enumerate(matching_vertices(m)) if (u < v)]
+
 include("lp.jl")
 include("maximum_weight_matching.jl")
 include("blossomv.jl")
diff --git a/test/runtests.jl b/test/runtests.jl
index 0beb14f..db53099 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -6,255 +6,300 @@ using JuMP
 using LinearAlgebra: I
 
 @testset "GraphsMatching" begin
+    @testset "accessors" begin
+        match = MatchingResult(45, [4, -1, 7, 1, 6, 5, 3, -1])
 
-@testset "maximum_weight_matching" begin
-    g = complete_graph(3)
-    w = [
-         1 2 1
-         1 1 1
-         3 1 1
-        ]
-    match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w)
-    @test match.mate[1] == 3
-    @test match.weight ≈ 3
-
-    g = complete_graph(3)
-    w = zeros(3,3)
-    w[1,2] = 1
-    w[3,2] = 1
-    w[1,3] = 1
-    match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),w)
-    @test match.weight ≈ 1
-
-
-    g = Graph(4)
-    add_edge!(g, 1,3)
-    add_edge!(g, 1,4)
-    add_edge!(g, 2,4)
-
-    w =zeros(4,4)
-    w[1,3] = 1
-    w[1,4] = 3
-    w[2,4] = 1
-
-    match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),w)
-    @test match.weight ≈ 3
-    @test match.mate[1] == 4
-    @test match.mate[2] == -1
-    @test match.mate[3] == -1
-    @test match.mate[4] == 1
-
-    g = Graph(4)
-    add_edge!(g, 1,2)
-    add_edge!(g, 2,3)
-    add_edge!(g, 3,1)
-    add_edge!(g, 3,4)
-    match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0))
-    @test match.weight ≈ 2
-    @test match.mate[1] == 2
-    @test match.mate[2] == 1
-    @test match.mate[3] == 4
-    @test match.mate[4] == 3
-
-    w = zeros(4,4)
-    w[1,2] = 1
-    w[2,3] = 1
-    w[1,3] = 1
-    w[3,4] = 1
-
-    match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w)
-    @test match.weight ≈ 2
-    @test match.mate[1] == 2
-    @test match.mate[2] == 1
-    @test match.mate[3] == 4
-    @test match.mate[4] == 3
-
-    w = zeros(4,4)
-    w[1,2] = 1
-    w[2,3] = 1
-    w[1,3] = 5
-    w[3,4] = 1
-
-    match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),w)
-    @test match.weight ≈ 5
-    @test match.mate[1] == 3
-    @test match.mate[2] == -1
-    @test match.mate[3] == 1
-    @test match.mate[4] == -1
-end
-
-
-@testset "maximum_weight_maximal_matching" begin
-
-    g = complete_bipartite_graph(2,2)
-    w = zeros(4,4)
-    w[1,3] = 10.
-    w[1,4] = 1.
-    w[2,3] = 2.
-    w[2,4] = 11.
-    match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0))
-    @test match.weight ≈ 21
-    @test match.mate[1] == 3
-    @test match.mate[3] == 1
-    @test match.mate[2] == 4
-    @test match.mate[4] == 2
-
-    g =complete_bipartite_graph(2,4)
-    w =zeros(6,6)
-    w[1,3] = 10
-    w[1,4] = 0.5
-    w[2,3] = 11
-    w[2,4] = 1
-    match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0))
-    @test match.weight ≈ 11.5
-    @test match.mate[1] == 4
-    @test match.mate[4] == 1
-    @test match.mate[2] == 3
-    @test match.mate[3] == 2
-
-    g =complete_bipartite_graph(2,6)
-    w =zeros(8,8)
-    w[1,3] = 10
-    w[1,4] = 0.5
-    w[2,3] = 11
-    w[2,4] = 1
-    w[2,5] = -1
-    w[2,6] = -1
-    match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), cutoff=0)
-    @test match.weight ≈ 11.5
-    @test match.mate[1] == 4
-    @test match.mate[4] == 1
-    @test match.mate[2] == 3
-    @test match.mate[3] == 2
-
-    g =complete_bipartite_graph(4,2)
-    w = zeros(6,6)
-    w[3,5] = 10
-    w[3,6] = 0.5
-    w[2,5] = 11
-    w[1,6] = 1
-    w[1,5] = -1
-
-    match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), cutoff=0)
-    @test match.weight ≈ 12
-    @test match.mate[1] == 6
-    @test match.mate[2] == 5
-    @test match.mate[3] == -1
-    @test match.mate[4] == -1
-    @test match.mate[5] == 2
-    @test match.mate[6] == 1
-
-
-    g = complete_bipartite_graph(2, 2)
-    w = zeros(4, 4)
-    w[1, 3] = 10.
-    w[1, 4] = 1.
-    w[2, 3] = 2.
-    w[2, 4] = 11.
-    match = maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm())
-    @test match.weight ≈ 21
-    @test match.mate[1] == 3
-    @test match.mate[3] == 1
-    @test match.mate[2] == 4
-    @test match.mate[4] == 2
-
-    g = complete_graph(3)
-    w = zeros(3, 3)
-    @test ! is_bipartite(g)
-    @test_throws ErrorException maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm())
-
-    g = complete_bipartite_graph(2, 4)
-    w = zeros(6, 6)
-    w[1, 3] = 10
-    w[1, 4] = 0.5
-    w[2, 3] = 11
-    w[2, 4] = 1
-    match = maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm())
-    @test match.weight ≈ 11.5
-
-    g = Graph(4)
-    add_edge!(g, 1, 3)
-    add_edge!(g, 1, 4)
-    add_edge!(g, 2, 4)
-    w = zeros(4, 4)
-    w[1, 3] = 1
-    w[1, 4] = 3
-    w[2, 4] = 1
-    match = maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm())
-    @test match.weight ≈ 2
-
-end
+        @test weight(match) == 45
 
+        @test is_matched_vertex(match, 3) == true
+        @test is_matched_vertex(match, 2) == false
 
+        @test matching_vertex(match, 5) == 6
+        @test matching_vertex(match, 8) == -1
 
-@testset "minimum_weight_perfect_matching" begin
-
-    w = Dict(Edge(1,2)=> 500)
-    g =Graph(2)
-    add_edge!(g,1,2)
-    match = minimum_weight_perfect_matching(g, w)
-    @test match.mate[1] == 2
-
-
-    w=Dict( Edge(1,2)=>500,
-           Edge(1,3)=>600,
-           Edge(2,3)=>700,
-           Edge(3,4)=>100,
-           Edge(2,4)=>1000)
-
-    g = complete_graph(4)
-    match = minimum_weight_perfect_matching(g, w)
-    @test match.mate[1] == 2
-    @test match.mate[2] == 1
-    @test match.mate[3] == 4
-    @test match.mate[4] == 3
-    @test match.weight ≈ 600
-
-    w = Dict(
-             Edge(1, 2) => 500,
-             Edge(1, 3) => 400,
-             Edge(2, 3) => 300,
-             Edge(3, 4) => 1000,
-             Edge(2, 4) => 1000
-            )
-    g = complete_graph(4)
-    match = minimum_weight_perfect_matching(g, w)
-    @test match.mate[1] == 3
-    @test match.mate[2] == 4
-    @test match.mate[3] == 1
-    @test match.mate[4] == 2
-    @test match.weight ≈ 1400
-
-    g =complete_bipartite_graph(2,2)
-    w =Dict{Edge,Float64}()
-    w[Edge(1,3)] = -10
-    w[Edge(1,4)] = -0.5
-    w[Edge(2,3)] = -11
-    w[Edge(2,4)] = -1
-
-    match = minimum_weight_perfect_matching(g, w)
-    @test match.mate[1] == 4
-    @test match.mate[4] == 1
-    @test match.mate[2] == 3
-    @test match.mate[3] == 2
-    @test match.weight ≈ -11.5
-
-
-    g = complete_graph(4)
-    w = Dict{Edge,Float64}()
-    w[Edge(1,3)] = 10
-    w[Edge(1,4)] = 0.5
-    w[Edge(2,3)] = 11
-    w[Edge(2,4)] = 2
-    w[Edge(1,2)] = 100
-
-    match = minimum_weight_perfect_matching(g, w, 50)
-    @test match.mate[1] == 4
-    @test match.mate[4] == 1
-    @test match.mate[2] == 3
-    @test match.mate[3] == 2
-    @test match.weight ≈ 11.5
-end
+        @test matching_vertices(match) == [4, -1, 7, 1, 6, 5, 3, -1]
 
+        m_edges = matched_edges(match)
+        @test length(m_edges) == 3
+        @test Edge(1, 4) ∈ m_edges
+        @test Edge(3, 7) ∈ m_edges
+        @test Edge(5, 6) ∈ m_edges
+    end
 
+    @testset "maximum_weight_matching" begin
+        g = complete_graph(3)
+        w = [
+            1 2 1
+            1 1 1
+            3 1 1
+        ]
+        match = maximum_weight_matching(
+            g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w
+        )
+        @test matching_vertex(match, 1) == 3
+        @test weight(match) ≈ 3
+
+        g = complete_graph(3)
+        w = zeros(3, 3)
+        w[1, 2] = 1
+        w[3, 2] = 1
+        w[1, 3] = 1
+        match = maximum_weight_matching(
+            g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w
+        )
+        @test weight(match) ≈ 1
+
+        g = Graph(4)
+        add_edge!(g, 1, 3)
+        add_edge!(g, 1, 4)
+        add_edge!(g, 2, 4)
+
+        w = zeros(4, 4)
+        w[1, 3] = 1
+        w[1, 4] = 3
+        w[2, 4] = 1
+
+        match = maximum_weight_matching(
+            g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w
+        )
+        @test weight(match) ≈ 3
+        @test matching_vertex(match, 1) == 4
+        @test matching_vertex(match, 2) == -1
+        @test matching_vertex(match, 3) == -1
+        @test matching_vertex(match, 4) == 1
+
+        g = Graph(4)
+        add_edge!(g, 1, 2)
+        add_edge!(g, 2, 3)
+        add_edge!(g, 3, 1)
+        add_edge!(g, 3, 4)
+        match = maximum_weight_matching(
+            g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0)
+        )
+        @test weight(match) ≈ 2
+        @test matching_vertex(match, 1) == 2
+        @test matching_vertex(match, 2) == 1
+        @test matching_vertex(match, 3) == 4
+        @test matching_vertex(match, 4) == 3
+
+        w = zeros(4, 4)
+        w[1, 2] = 1
+        w[2, 3] = 1
+        w[1, 3] = 1
+        w[3, 4] = 1
+
+        match = maximum_weight_matching(
+            g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w
+        )
+        @test weight(match) ≈ 2
+        @test matching_vertex(match, 1) == 2
+        @test matching_vertex(match, 2) == 1
+        @test matching_vertex(match, 3) == 4
+        @test matching_vertex(match, 4) == 3
+
+        w = zeros(4, 4)
+        w[1, 2] = 1
+        w[2, 3] = 1
+        w[1, 3] = 5
+        w[3, 4] = 1
+
+        match = maximum_weight_matching(
+            g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w
+        )
+        @test weight(match) ≈ 5
+        @test matching_vertex(match, 1) == 3
+        @test matching_vertex(match, 2) == -1
+        @test matching_vertex(match, 3) == 1
+        @test matching_vertex(match, 4) == -1
+    end
+
+    @testset "maximum_weight_maximal_matching" begin
+        g = complete_bipartite_graph(2, 2)
+        w = zeros(4, 4)
+        w[1, 3] = 10.0
+        w[1, 4] = 1.0
+        w[2, 3] = 2.0
+        w[2, 4] = 11.0
+        match = maximum_weight_maximal_matching(
+            g,
+            w;
+            algorithm=LPAlgorithm(),
+            optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),
+        )
+        @test weight(match) ≈ 21
+        @test matching_vertex(match, 1) == 3
+        @test matching_vertex(match, 3) == 1
+        @test matching_vertex(match, 2) == 4
+        @test matching_vertex(match, 4) == 2
+
+        g = complete_bipartite_graph(2, 4)
+        w = zeros(6, 6)
+        w[1, 3] = 10
+        w[1, 4] = 0.5
+        w[2, 3] = 11
+        w[2, 4] = 1
+        match = maximum_weight_maximal_matching(
+            g,
+            w;
+            algorithm=LPAlgorithm(),
+            optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),
+        )
+        @test weight(match) ≈ 11.5
+        @test matching_vertex(match, 1) == 4
+        @test matching_vertex(match, 4) == 1
+        @test matching_vertex(match, 2) == 3
+        @test matching_vertex(match, 3) == 2
+
+        g = complete_bipartite_graph(2, 6)
+        w = zeros(8, 8)
+        w[1, 3] = 10
+        w[1, 4] = 0.5
+        w[2, 3] = 11
+        w[2, 4] = 1
+        w[2, 5] = -1
+        w[2, 6] = -1
+        match = maximum_weight_maximal_matching(
+            g,
+            w;
+            algorithm=LPAlgorithm(),
+            optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),
+            cutoff=0,
+        )
+        @test weight(match) ≈ 11.5
+        @test matching_vertex(match, 1) == 4
+        @test matching_vertex(match, 4) == 1
+        @test matching_vertex(match, 2) == 3
+        @test matching_vertex(match, 3) == 2
+
+        g = complete_bipartite_graph(4, 2)
+        w = zeros(6, 6)
+        w[3, 5] = 10
+        w[3, 6] = 0.5
+        w[2, 5] = 11
+        w[1, 6] = 1
+        w[1, 5] = -1
+
+        match = maximum_weight_maximal_matching(
+            g,
+            w;
+            algorithm=LPAlgorithm(),
+            optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),
+            cutoff=0,
+        )
+        @test weight(match) ≈ 12
+        @test matching_vertex(match, 1) == 6
+        @test matching_vertex(match, 2) == 5
+        @test matching_vertex(match, 3) == -1
+        @test matching_vertex(match, 4) == -1
+        @test matching_vertex(match, 5) == 2
+        @test matching_vertex(match, 6) == 1
+
+        g = complete_bipartite_graph(2, 2)
+        w = zeros(4, 4)
+        w[1, 3] = 10.0
+        w[1, 4] = 1.0
+        w[2, 3] = 2.0
+        w[2, 4] = 11.0
+        match = maximum_weight_maximal_matching(g, w; algorithm=HungarianAlgorithm())
+        @test weight(match) ≈ 21
+        @test matching_vertex(match, 1) == 3
+        @test matching_vertex(match, 3) == 1
+        @test matching_vertex(match, 2) == 4
+        @test matching_vertex(match, 4) == 2
+
+        g = complete_graph(3)
+        w = zeros(3, 3)
+        @test !is_bipartite(g)
+        @test_throws ErrorException maximum_weight_maximal_matching(
+            g, w, algorithm=HungarianAlgorithm()
+        )
+
+        g = complete_bipartite_graph(2, 4)
+        w = zeros(6, 6)
+        w[1, 3] = 10
+        w[1, 4] = 0.5
+        w[2, 3] = 11
+        w[2, 4] = 1
+        match = maximum_weight_maximal_matching(g, w; algorithm=HungarianAlgorithm())
+        @test weight(match) ≈ 11.5
+
+        g = Graph(4)
+        add_edge!(g, 1, 3)
+        add_edge!(g, 1, 4)
+        add_edge!(g, 2, 4)
+        w = zeros(4, 4)
+        w[1, 3] = 1
+        w[1, 4] = 3
+        w[2, 4] = 1
+        match = maximum_weight_maximal_matching(g, w; algorithm=HungarianAlgorithm())
+        @test weight(match) ≈ 2
+    end
+
+    @testset "minimum_weight_perfect_matching" begin
+        w = Dict(Edge(1, 2) => 500)
+        g = Graph(2)
+        add_edge!(g, 1, 2)
+        match = minimum_weight_perfect_matching(g, w)
+        @test matching_vertex(match, 1) == 2
+
+        w = Dict(
+            Edge(1, 2) => 500,
+            Edge(1, 3) => 600,
+            Edge(2, 3) => 700,
+            Edge(3, 4) => 100,
+            Edge(2, 4) => 1000,
+        )
+
+        g = complete_graph(4)
+        match = minimum_weight_perfect_matching(g, w)
+        @test matching_vertex(match, 1) == 2
+        @test matching_vertex(match, 2) == 1
+        @test matching_vertex(match, 3) == 4
+        @test matching_vertex(match, 4) == 3
+        @test weight(match) ≈ 600
+
+        w = Dict(
+            Edge(1, 2) => 500,
+            Edge(1, 3) => 400,
+            Edge(2, 3) => 300,
+            Edge(3, 4) => 1000,
+            Edge(2, 4) => 1000,
+        )
+        g = complete_graph(4)
+        match = minimum_weight_perfect_matching(g, w)
+        @test matching_vertex(match, 1) == 3
+        @test matching_vertex(match, 2) == 4
+        @test matching_vertex(match, 3) == 1
+        @test matching_vertex(match, 4) == 2
+        @test weight(match) ≈ 1400
+
+        g = complete_bipartite_graph(2, 2)
+        w = Dict{Edge,Float64}()
+        w[Edge(1, 3)] = -10
+        w[Edge(1, 4)] = -0.5
+        w[Edge(2, 3)] = -11
+        w[Edge(2, 4)] = -1
+
+        match = minimum_weight_perfect_matching(g, w)
+        @test matching_vertex(match, 1) == 4
+        @test matching_vertex(match, 4) == 1
+        @test matching_vertex(match, 2) == 3
+        @test matching_vertex(match, 3) == 2
+        @test weight(match) ≈ -11.5
+
+        g = complete_graph(4)
+        w = Dict{Edge,Float64}()
+        w[Edge(1, 3)] = 10
+        w[Edge(1, 4)] = 0.5
+        w[Edge(2, 3)] = 11
+        w[Edge(2, 4)] = 2
+        w[Edge(1, 2)] = 100
+
+        match = minimum_weight_perfect_matching(g, w, 50)
+        @test matching_vertex(match, 1) == 4
+        @test matching_vertex(match, 4) == 1
+        @test matching_vertex(match, 2) == 3
+        @test matching_vertex(match, 3) == 2
+        @test weight(match) ≈ 11.5
+    end
 end