Skip to content

Commit e1c8879

Browse files
committed
Add tree-decomposition-based partitioning.
1 parent e882d8f commit e1c8879

3 files changed

Lines changed: 216 additions & 0 deletions

File tree

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ authors = ["Jordan Jalving <jhjalving@gmail.com>"]
55
repo = "https://github.com/plasmo-dev/Plasmo.jl.git"
66

77
[deps]
8+
CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8"
89
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
910
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
1011
DistributedArrays = "aaf54ef3-cdf8-58ed-94cc-d582ad619b94"
@@ -21,6 +22,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2122
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
2223

2324
[compat]
25+
CliqueTrees = "1"
2426
DataStructures = "0.18, 0.19"
2527
DistributedArrays = "0.6"
2628
GraphOptInterface = "0.1"

src/Plasmo.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export OptiGraph,
7979
n_subpartitions,
8080
all_subpartitions,
8181

82+
# clique tree
83+
84+
apply_clique_tree!,
85+
8286
# aggregate
8387

8488
aggregate,
@@ -173,6 +177,8 @@ include("graph_functions/topology.jl")
173177

174178
include("graph_functions/partition.jl")
175179

180+
include("graph_functions/cliquetree.jl")
181+
176182
include("utils.jl")
177183

178184
include("distributed/local_conversions.jl")

src/graph_functions/cliquetree.jl

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import CliqueTrees:
2+
BipartiteGraph,
3+
cliquetree,
4+
cliquetree!,
5+
linegraph,
6+
neighbors,
7+
parentindex,
8+
residual,
9+
reverse,
10+
rootindex,
11+
separator
12+
13+
using JuMP: GenericAffExpr, add_to_expression!
14+
15+
function get_edge_to_node(graph::OptiGraph)
16+
nodes = all_nodes(graph)
17+
edges = all_edges(graph)
18+
node_to_idx = Dict(node => i for (i, node) in enumerate(nodes))
19+
20+
I = Int[]
21+
J = Int[]
22+
23+
for (e, edge) in enumerate(edges)
24+
for node in all_nodes(edge)
25+
push!(I, node_to_idx[node])
26+
push!(J, e)
27+
end
28+
end
29+
30+
matrix = sparse(I, J, ones(Int, length(I)), length(nodes), length(edges))
31+
return BipartiteGraph(matrix)
32+
end
33+
34+
function opti_clique_tree(graph::OptiGraph; kw...)
35+
nodes = all_nodes(graph)
36+
edges = all_edges(graph)
37+
38+
# Construct hypergraph
39+
edge_to_node = get_edge_to_node(graph)
40+
node_to_edge = reverse(edge_to_node)
41+
42+
# Find integral nodes
43+
int_nodes = [
44+
i for i in eachindex(nodes)
45+
if any(v -> JuMP.is_binary(v) || JuMP.is_integer(v), JuMP.all_variables(nodes[i]))
46+
]
47+
48+
# Construct line graph, forcing integer nodes to form a clique
49+
node_to_node = sparse(linegraph(node_to_edge, edge_to_node))
50+
node_to_node[int_nodes, int_nodes] .= 1
51+
52+
# Construct clique tree
53+
perm, tree = cliquetree(node_to_node; kw...)
54+
55+
# Pivot clique tree, moving integer nodes to root
56+
if !isempty(int_nodes)
57+
invp = invperm(perm)
58+
permute!(perm, cliquetree!(tree, invp[int_nodes]))
59+
end
60+
61+
# Assign each edge to its highest containing bag
62+
edge_to_bag = zeros(Int, length(edges))
63+
64+
for j in eachindex(tree)
65+
for v in residual(tree, j)
66+
for e in neighbors(node_to_edge, perm[v])
67+
if edge_to_bag[e] == 0
68+
edge_to_bag[e] = j
69+
end
70+
end
71+
end
72+
end
73+
74+
return perm, tree, edge_to_bag
75+
end
76+
77+
function apply_clique_tree!(
78+
graph::OptiGraph,
79+
perm::AbstractVector{<:Integer},
80+
tree,
81+
edge_to_bag::AbstractVector{<:Integer}
82+
)
83+
optinodes = all_nodes(graph)[perm]
84+
optiedges = all_edges(graph)
85+
86+
empty!(graph.optinodes)
87+
empty!(graph.optiedges)
88+
89+
V = NodeVariableRef
90+
bag_to_var_map = [Dict{V, V}() for _ in eachindex(tree)]
91+
bag_to_graph = Vector{OptiGraph}(undef, length(tree))
92+
93+
# For each bag in the clique tree, construct a
94+
# subgraph on the residual
95+
for j in eachindex(tree)
96+
subgraph = bag_to_graph[j] = OptiGraph()
97+
98+
for v in residual(tree, j)
99+
add_node!(subgraph, optinodes[v])
100+
end
101+
102+
set_to_node_objectives(subgraph)
103+
add_subgraph!(graph, subgraph)
104+
end
105+
106+
# Link each bag with its parent using equality constraints
107+
for j in reverse(eachindex(tree))
108+
for v in residual(tree, j)
109+
node = optinodes[v]
110+
111+
for var in JuMP.all_variables(node)
112+
bag_to_var_map[j][var] = var
113+
end
114+
end
115+
116+
k = parentindex(tree, j)
117+
118+
if !isnothing(k)
119+
@optinode(bag_to_graph[j], _sep_copy_node)
120+
121+
for v in separator(tree, j)
122+
node = optinodes[v]
123+
124+
for orig_var in JuMP.all_variables(node)
125+
copy_var = @variable(bag_to_graph[j][:_sep_copy_node])
126+
bag_to_var_map[j][orig_var] = copy_var
127+
@linkconstraint(graph, copy_var == bag_to_var_map[k][orig_var])
128+
end
129+
end
130+
end
131+
end
132+
133+
# Add internal constraints to each bag
134+
for (e, j) in enumerate(edge_to_bag)
135+
for con in JuMP.all_constraints(optiedges[e])
136+
con_obj = JuMP.constraint_object(con)
137+
_add_substituted_constraint!(con_obj, bag_to_graph[j], bag_to_var_map[j])
138+
end
139+
end
140+
141+
root = rootindex(tree)
142+
return bag_to_graph[root]
143+
end
144+
145+
"""
146+
apply_clique_tree!(graph::OptiGraph; kw...)
147+
148+
Transform an opti-graph into a tree of subgraphs.
149+
"""
150+
function apply_clique_tree!(graph::OptiGraph; kw...)
151+
perm, tree, edge_to_bag = opti_clique_tree(graph; kw...)
152+
return apply_clique_tree!(graph, perm, tree, edge_to_bag)
153+
end
154+
155+
function _add_substituted_constraint!(
156+
con_obj::JuMP.ScalarConstraint{GenericAffExpr{Float64, V}, S},
157+
subgraph::OptiGraph,
158+
var_map::Dict{V, V}
159+
) where {V <: JuMP.AbstractVariableRef, S}
160+
new_expr = GenericAffExpr{Float64, V}()
161+
162+
first_node = nothing
163+
is_link = false
164+
165+
for (var, coef) in con_obj.func.terms
166+
sub_var = var_map[var]
167+
add_to_expression!(new_expr, coef, sub_var)
168+
node = JuMP.owner_model(sub_var)
169+
170+
if isnothing(first_node)
171+
first_node = node
172+
elseif node != first_node
173+
is_link = true
174+
end
175+
end
176+
177+
new_expr.constant = con_obj.func.constant
178+
179+
if is_link
180+
_add_link_constraint!(subgraph, new_expr, con_obj.set)
181+
else
182+
_add_node_constraint!(first_node, new_expr, con_obj.set)
183+
end
184+
end
185+
186+
function _add_node_constraint!(node::OptiNode, expr::GenericAffExpr, set::MOI.LessThan)
187+
@constraint(node, expr <= set.upper)
188+
end
189+
190+
function _add_node_constraint!(node::OptiNode, expr::GenericAffExpr, set::MOI.GreaterThan)
191+
@constraint(node, expr >= set.lower)
192+
end
193+
194+
function _add_node_constraint!(node::OptiNode, expr::GenericAffExpr, set::MOI.EqualTo)
195+
@constraint(node, expr == set.value)
196+
end
197+
198+
function _add_link_constraint!(sg::OptiGraph, expr::GenericAffExpr, set::MOI.LessThan)
199+
@linkconstraint(sg, expr <= set.upper)
200+
end
201+
202+
function _add_link_constraint!(sg::OptiGraph, expr::GenericAffExpr, set::MOI.GreaterThan)
203+
@linkconstraint(sg, expr >= set.lower)
204+
end
205+
206+
function _add_link_constraint!(sg::OptiGraph, expr::GenericAffExpr, set::MOI.EqualTo)
207+
@linkconstraint(sg, expr == set.value)
208+
end

0 commit comments

Comments
 (0)