From 3127afa8115e10d9ed1d90865976fc782aaee113 Mon Sep 17 00:00:00 2001 From: Martin Lindner Date: Wed, 10 Jan 2024 08:08:22 +0100 Subject: [PATCH 1/3] Add optimal control example with static partition example --- examples/optimal_control_static_partition.jl | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/optimal_control_static_partition.jl diff --git a/examples/optimal_control_static_partition.jl b/examples/optimal_control_static_partition.jl new file mode 100644 index 0000000..d80db2a --- /dev/null +++ b/examples/optimal_control_static_partition.jl @@ -0,0 +1,60 @@ +# Example demonstrating the use of overlap to solve a long horizon control problem +# Uses a static partitioning of the graph (useful if KaHyPar is not available) +using Plasmo, Ipopt +using SchwarzOpt + +T = 200 # number of time points +d = sin.(1:T) # a disturbance vector +imbalance = 0.1 # partition imbalance +distance = 2 # expand distance +n_parts = 10 # number of partitions + +# Create the model (an optigraph) +graph = OptiGraph() + +@optinode(graph, state[1:T]) +@optinode(graph, control[1:(T-1)]) + +for (i, node) in enumerate(state) + @variable(node, x) + @constraint(node, x >= 0) + @objective(node, Min, x^2) #- 2*x*d[i]) +end +for node in control + @variable(node, u) + @constraint(node, u >= -1000) + @objective(node, Min, u^2) +end +n1 = state[1] +@constraint(n1, n1[:x] == 0) + +for i in 1:(T-1) + @linkconstraint(graph, state[i][:x] + control[i][:u] + d[i] == state[i+1][:x]) +end + +hypergraph, hyper_map = hyper_graph(graph) # create hypergraph object based on graph +# Create an equally sized partition based on the number of time points and number of partitions. +N_node = 2 * T - 1 +partition_vector = repeat(1:n_parts, inner=N_node ÷ n_parts) +remaining = N_node % n_parts +if remaining > 0 + partition_vector = [partition_vector; repeat(1:remaining)] +end + +# apply partition to graph +partition = Partition(hypergraph, partition_vector, hyper_map) +apply_partition!(graph, partition) + +# calculate subproblems using expansion distance +subs = subgraphs(graph) +expanded_subgraphs = Plasmo.expand.(graph, subs, distance) +sub_optimizer = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0) + +# optimize using schwarz overlapping decomposition +optimizer = SchwarzOpt.optimize!( + graph; + subgraphs=expanded_subgraphs, + sub_optimizer=sub_optimizer, + max_iterations=100, + mu=100.0, +) From fbd99bbdc1ecf0ada3dafb6032209ab214b99dab Mon Sep 17 00:00:00 2001 From: Martin Lindner Date: Wed, 10 Jan 2024 08:09:19 +0100 Subject: [PATCH 2/3] Update value and dual retrieval to Plasmo 0.5.4 --- src/optimizer.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/optimizer.jl b/src/optimizer.jl index 0ba928f..596694d 100644 --- a/src/optimizer.jl +++ b/src/optimizer.jl @@ -302,8 +302,8 @@ function _do_iteration(subproblem_graph::OptiGraph) x_sub = subproblem_graph.ext[:x_map] # primal variables to update l_sub = subproblem_graph.ext[:l_map] # dual variables to update - xk = Dict(key => value(subproblem_graph, val) for (key, val) in x_sub) - lk = Dict(key => dual(subproblem_graph, val) for (key, val) in l_sub) + xk = Dict(key => value(val) for (key, val) in x_sub) + lk = Dict(key => dual(val) for (key, val) in l_sub) return xk, lk end @@ -327,7 +327,7 @@ function _calculate_primal_feasibility(optimizer) node = optinode(term) graph = optimizer.node_subgraph_map[node] subproblem_graph = optimizer.expanded_subgraph_map[graph] - val += coeff * value(subproblem_graph, term) + val += coeff * value(term) end push!(prf, val - linkcon.set.value) end @@ -457,7 +457,7 @@ function optimize!(optimizer::Optimizer) JuMP.set_start_value.( Ref(subproblem), all_variables(subproblem), - value.(Ref(subproblem), all_variables(subproblem)), + value.(all_variables(subproblem)), ) end end From 20454f049b9c373b0c3c73cfee386e0f7e760d98 Mon Sep 17 00:00:00 2001 From: Martin Lindner Date: Tue, 23 Jan 2024 12:16:21 +0100 Subject: [PATCH 3/3] Add verbosity and example --- examples/optimal_control_static_partition.jl | 26 +++++++++++--------- src/optimizer.jl | 26 ++++++++++++++------ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/examples/optimal_control_static_partition.jl b/examples/optimal_control_static_partition.jl index d80db2a..d29c1aa 100644 --- a/examples/optimal_control_static_partition.jl +++ b/examples/optimal_control_static_partition.jl @@ -3,17 +3,17 @@ using Plasmo, Ipopt using SchwarzOpt -T = 200 # number of time points +T = 8 # number of time points d = sin.(1:T) # a disturbance vector imbalance = 0.1 # partition imbalance distance = 2 # expand distance -n_parts = 10 # number of partitions +n_parts = 5 # number of partitions # Create the model (an optigraph) -graph = OptiGraph() +graph_schwarz = OptiGraph() -@optinode(graph, state[1:T]) -@optinode(graph, control[1:(T-1)]) +@optinode(graph_schwarz, state[1:T]) +@optinode(graph_schwarz, control[1:(T-1)]) for (i, node) in enumerate(state) @variable(node, x) @@ -29,10 +29,10 @@ n1 = state[1] @constraint(n1, n1[:x] == 0) for i in 1:(T-1) - @linkconstraint(graph, state[i][:x] + control[i][:u] + d[i] == state[i+1][:x]) + @linkconstraint(graph_schwarz, state[i][:x] + control[i][:u] + d[i] == state[i+1][:x]) end -hypergraph, hyper_map = hyper_graph(graph) # create hypergraph object based on graph +hypergraph, hyper_map = hyper_graph(graph_schwarz) # create hypergraph object based on graph # Create an equally sized partition based on the number of time points and number of partitions. N_node = 2 * T - 1 partition_vector = repeat(1:n_parts, inner=N_node ÷ n_parts) @@ -41,18 +41,20 @@ if remaining > 0 partition_vector = [partition_vector; repeat(1:remaining)] end + # apply partition to graph partition = Partition(hypergraph, partition_vector, hyper_map) -apply_partition!(graph, partition) - +## +@run apply_partition!(graph_schwarz, partition) +## # calculate subproblems using expansion distance -subs = subgraphs(graph) -expanded_subgraphs = Plasmo.expand.(graph, subs, distance) +subs = subgraphs(graph_schwarz) +expanded_subgraphs = Plasmo.expand.(graph_schwarz, subs, distance) sub_optimizer = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0) # optimize using schwarz overlapping decomposition optimizer = SchwarzOpt.optimize!( - graph; + graph_schwarz; subgraphs=expanded_subgraphs, sub_optimizer=sub_optimizer, max_iterations=100, diff --git a/src/optimizer.jl b/src/optimizer.jl index 596694d..27e180c 100644 --- a/src/optimizer.jl +++ b/src/optimizer.jl @@ -291,13 +291,15 @@ end function _do_iteration(subproblem_graph::OptiGraph) Plasmo.optimize!(subproblem_graph) term_status = termination_status(subproblem_graph) + # Create label of subproblem_graph by concatenating labels of optinodes + label = join(map(x -> x.label, all_nodes(subproblem_graph)), "_") !( term_status in [ MOI.TerminationStatusCode(4), MOI.TerminationStatusCode(1), MOI.TerminationStatusCode(10), ] - ) && @warn("Suboptimal solution detected for subproblem with status $term_status") + ) && @warn("Suboptimal solution detected for subproblem with status $term_status: $label") x_sub = subproblem_graph.ext[:x_map] # primal variables to update l_sub = subproblem_graph.ext[:l_map] # dual variables to update @@ -323,13 +325,20 @@ function _calculate_primal_feasibility(optimizer) val = 0 linkcon = constraint_object(linkref) terms = linkcon.func.terms - for (term, coeff) in terms - node = optinode(term) - graph = optimizer.node_subgraph_map[node] - subproblem_graph = optimizer.expanded_subgraph_map[graph] - val += coeff * value(term) + try + for (term, coeff) in terms + node = optinode(term) + graph = optimizer.node_subgraph_map[node] + subproblem_graph = optimizer.expanded_subgraph_map[graph] + val += coeff * value(term) + end + push!(prf, val - linkcon.set.value) + catch + println("Error in calculating primal feasibility for linkconstraint: $linkcon") + println("linkcon.set: $(linkcon.set)") + println("linkcon.func: $(linkcon.func)") + println("linkcon.func.terms: $(linkcon.func.terms)") end - push!(prf, val - linkcon.set.value) end return prf end @@ -403,7 +412,8 @@ function optimize!(optimizer::Optimizer) #Do iteration for each subproblem optimizer.timers.update_subproblem_time += @elapsed begin - for subproblem_graph in optimizer.subproblem_graphs + for (i_sp, subproblem_graph) in enumerate(optimizer.subproblem_graphs) + println("Updating subproblem $i_sp") _update_subproblem!(optimizer, subproblem_graph) end end