Skip to content

Commit 75e1b74

Browse files
Merge pull request #72 from behzadd1992/fix/carpanzano-deterministic-tearing-bugfix
Fix non-deterministic behavior and incorrect variable selection in Carpanzano tearing algorithm
2 parents ba367df + 1b6c126 commit 75e1b74

3 files changed

Lines changed: 68 additions & 4 deletions

File tree

src/carpanzano_tearing.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ function (alg::CarpanzanoTearing)(structure::SystemStructure)
5252
full_var_eq_matching = copy(var_eq_matching)
5353
var_sccs = find_var_sccs(graph, var_eq_matching)
5454

55-
active_vars = Set{Int}()
56-
active_eqs = Set{Int}()
55+
active_vars = OrderedSet{Int}()
56+
active_eqs = OrderedSet{Int}()
5757
for vars in var_sccs
5858
for var in vars
5959
# Identify variables and equations in this SCC
@@ -87,7 +87,7 @@ In the context of the paper, `structure.graph` is the associated bipartite graph
8787
"""
8888
function find_single_solvable_eq!(
8989
structure::SystemStructure, var_eq_matching::MatchingT,
90-
active_vars::Set{Int}, active_eqs::Set{Int}, condition::F = _ -> true;
90+
active_vars::AbstractSet{Int}, active_eqs::AbstractSet{Int}, condition::F = _ -> true;
9191
nbors_buffer::Vector{Int} = Int[]
9292
) where {F}
9393
(; graph, solvable_graph) = structure
@@ -116,7 +116,7 @@ all of `active_vars` to `unassigned`, and will be modified to match solvable var
116116
"""
117117
function carpanzano_tear_scc!(
118118
alg::CarpanzanoTearing, structure::SystemStructure, var_eq_matching::MatchingT,
119-
active_vars::Set{Int}, active_eqs::Set{Int}
119+
active_vars::AbstractSet{Int}, active_eqs::AbstractSet{Int}
120120
)
121121
# TODO: This is an implementation of algorithm A1 in the paper. Find an efficient
122122
# way to implement algorithm A2 and analyze the benefits.
@@ -211,6 +211,8 @@ function carpanzano_tear_scc!(
211211
solvable_cnt = count(in(active_eqs), 𝑑neighbors(solvable_graph, ivar))
212212
if iszero(alg_var) || cnt > max_incidence_cnt || cnt == max_incidence_cnt && solvable_cnt < min_solvable_cnt
213213
alg_var = ivar
214+
max_incidence_cnt = cnt
215+
min_solvable_cnt = solvable_cnt
214216
end
215217
end
216218

test/carpanzano_tearing.jl

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Tests for carpanzano_tearing.jl - determinism and Heuristic 2 correctness
2+
3+
using BipartiteGraphs
4+
import Graphs: add_edge!
5+
6+
struct TestSystemStructure <: StateSelection.SystemStructure
7+
graph::BipartiteGraph{Int,Nothing}
8+
solvable_graph::BipartiteGraph{Int,Nothing}
9+
var_to_diff::DiffGraph
10+
eq_to_diff::DiffGraph
11+
end
12+
13+
@testset "carpanzano_tearing" begin
14+
@testset "Heuristic 2 picks variable with maximum incidence" begin
15+
# Graph: 3 equations (source vertices), 3 variables (destination vertices).
16+
# Every edge is also present in the solvable_graph (all variables solvable in
17+
# all equations they appear in), so Heuristic 1 never finds a non-solvable
18+
# variable and falls through to Heuristic 2.
19+
#
20+
# Incidence of each variable (number of equations it appears in):
21+
# v1 (idx 1): eq1, eq2 => 2
22+
# v2 (idx 2): eq1, eq2, eq3 => 3 # maximum -> must be torn (algebraic)
23+
# v3 (idx 3): eq1, eq3 => 2
24+
#
25+
# Before the fix, max_incidence_cnt was never updated inside the Heuristic 2
26+
# loop, so every variable satisfied cnt > typemin(Int) and the last variable
27+
# in hash-iteration order was chosen -- non-deterministic and wrong.
28+
graph = BipartiteGraph(3, 3)
29+
for (eq, var) in [(1,1),(1,2),(1,3),(2,1),(2,2),(3,2),(3,3)]
30+
add_edge!(graph, eq, var)
31+
end
32+
solvable_graph = BipartiteGraph(3, 3)
33+
for (eq, var) in [(1,1),(1,2),(1,3),(2,1),(2,2),(3,2),(3,3)]
34+
add_edge!(solvable_graph, eq, var)
35+
end
36+
37+
structure = TestSystemStructure(
38+
graph, solvable_graph,
39+
DiffGraph(3), DiffGraph(3)
40+
)
41+
alg = StateSelection.CarpanzanoTearing()
42+
# complete() makes invview(var_eq_matching) available, which the algorithm uses
43+
# internally to find the variable matched to a solved equation.
44+
var_eq_matching = complete(
45+
Matching{Union{Unassigned, SelectedState}}(3), 3
46+
)
47+
active_vars = Set{Int}([1, 2, 3])
48+
active_eqs = Set{Int}([1, 2, 3])
49+
50+
StateSelection.carpanzano_tear_scc!(
51+
alg, structure, var_eq_matching, active_vars, active_eqs
52+
)
53+
54+
# v2 has the highest incidence (3 equations) -- Heuristic 2 must choose it as
55+
# the algebraic (torn) variable, leaving it unmatched.
56+
@test var_eq_matching[2] === unassigned
57+
# v1 and v3 must each be matched to some equation.
58+
@test var_eq_matching[1] isa Int
59+
@test var_eq_matching[3] isa Int
60+
end
61+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using SparseArrays
44
using Test
55

66
include("bareiss.jl")
7+
include("carpanzano_tearing.jl")
78

89
@testset "`get_new_mm`" begin
910
mm = SSel.CLIL.SparseMatrixCLIL(

0 commit comments

Comments
 (0)