You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
UnitCommitment.jl/src/solution/methods/ProgressiveHedging/optimize.jl

231 lines
6.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using MPI, Printf
using TimerOutputs
import JuMP
const to = TimerOutput()
function optimize!(model::JuMP.Model, method::ProgressiveHedging)::Nothing
mpi = MpiInfo(MPI.COMM_WORLD)
iterations = PHIterationInfo[]
consensus_vars = [var for var in all_variables(model) if is_binary(var)]
nvars = length(consensus_vars)
weights = ones(nvars)
if method.initial_weights !== nothing
weights = copy(method.initial_weights)
end
target = zeros(nvars)
if method.initial_target !== nothing
target = copy(method.initial_target)
end
params = PHSubProblemParams(
ρ = method.ρ,
λ = [method.λ for _ in 1:nvars],
target = target,
)
sp = PHSubProblem(model, model[:obj], consensus_vars, weights)
while true
iteration_time = @elapsed begin
solution = solve_subproblem(sp, params, method.inner_method)
MPI.Barrier(mpi.comm)
global_obj = compute_global_objective(mpi, solution)
target = compute_target(mpi, solution)
update_λ_and_residuals!(solution, params, target)
global_infeas = compute_global_infeasibility(solution, mpi)
global_residual = compute_global_residual(mpi, solution)
if has_numerical_issues(target)
break
end
end
total_elapsed_time =
compute_total_elapsed_time(iteration_time, iterations)
current_iteration = PHIterationInfo(
global_infeas = global_infeas,
global_obj = global_obj,
global_residual = global_residual,
iteration_number = length(iterations) + 1,
iteration_time = iteration_time,
sp_vals = solution.vals,
sp_obj = solution.obj,
target = target,
total_elapsed_time = total_elapsed_time,
)
push!(iterations, current_iteration)
print_progress(mpi, current_iteration, method.print_interval)
if should_stop(mpi, iterations, method.termination)
break
end
end
return
end
function compute_total_elapsed_time(
iteration_time::Float64,
iterations::Array{PHIterationInfo,1},
)::Float64
length(iterations) > 0 ?
current_total_time = last(iterations).total_elapsed_time :
current_total_time = 0
return current_total_time + iteration_time
end
function compute_global_objective(
mpi::MpiInfo,
s::PhSubProblemSolution,
)::Float64
global_obj = MPI.Allreduce(s.obj, MPI.SUM, mpi.comm)
global_obj /= mpi.nprocs
return global_obj
end
function compute_target(mpi::MpiInfo, s::PhSubProblemSolution)::Array{Float64,1}
sp_vals = s.vals
target = MPI.Allreduce(sp_vals, MPI.SUM, mpi.comm)
target = target / mpi.nprocs
return target
end
function compute_global_residual(mpi::MpiInfo, s::PhSubProblemSolution)::Float64
n_vars = length(s.vals)
local_residual_sum = abs.(s.residuals)
global_residual_sum = MPI.Allreduce(local_residual_sum, MPI.SUM, mpi.comm)
return sum(global_residual_sum) / n_vars
end
function compute_global_infeasibility(
solution::PhSubProblemSolution,
mpi::MpiInfo,
)::Float64
local_infeasibility = norm(solution.residuals)
global_infeas = MPI.Allreduce(local_infeasibility, MPI.SUM, mpi.comm)
return global_infeas
end
function solve_subproblem(
sp::PHSubProblem,
params::PHSubProblemParams,
method::SolutionMethod,
)::PhSubProblemSolution
G = length(sp.consensus_vars)
if norm(params.λ) < 1e-3
@objective(sp.mip, Min, sp.obj)
else
@objective(
sp.mip,
Min,
sp.obj +
sum(
sp.weights[g] *
params.λ[g] *
(sp.consensus_vars[g] - params.target[g]) for g in 1:G
) +
(params.ρ / 2) * sum(
sp.weights[g] * (sp.consensus_vars[g] - params.target[g])^2 for
g in 1:G
)
)
end
optimize!(sp.mip, method)
obj = objective_value(sp.mip)
sp_vals = value.(sp.consensus_vars)
return PhSubProblemSolution(obj = obj, vals = sp_vals, residuals = zeros(G))
end
function update_λ_and_residuals!(
solution::PhSubProblemSolution,
params::PHSubProblemParams,
target::Array{Float64,1},
)::Nothing
n_vars = length(solution.vals)
params.target = target
for n in 1:n_vars
solution.residuals[n] = solution.vals[n] - params.target[n]
params.λ[n] += params.ρ * solution.residuals[n]
end
end
function print_header(mpi::MpiInfo)::Nothing
if !mpi.root
return
end
@info "Solving via Progressive Hedging:"
@info @sprintf(
"%8s %20s %20s %14s %8s %8s",
"iter",
"obj",
"infeas",
"consensus",
"time-it",
"time"
)
end
function print_progress(
mpi::MpiInfo,
iteration::PHIterationInfo,
print_interval,
)::Nothing
if !mpi.root
return
end
if iteration.iteration_number % print_interval != 0
return
end
@info @sprintf(
"%8d %20.6e %20.6e %12.2f %% %8.2f %8.2f",
iteration.iteration_number,
iteration.global_obj,
iteration.global_infeas,
iteration.global_residual * 100,
iteration.iteration_time,
iteration.total_elapsed_time
)
end
function has_numerical_issues(target::Array{Float64,1})::Bool
if target == NaN
@warn "Numerical issues detected. Stopping."
return true
end
return false
end
function should_stop(
mpi::MpiInfo,
iterations::Array{PHIterationInfo,1},
termination::PHTermination,
)::Bool
if length(iterations) >= termination.max_iterations
if mpi.root
@info "Iteration limit reached. Stopping."
end
return true
end
if length(iterations) < termination.min_iterations
return false
end
if last(iterations).total_elapsed_time > termination.max_time
if mpi.root
@info "Time limit reached. Stopping."
end
return true
end
curr_it = last(iterations)
prev_it = iterations[length(iterations)-1]
if curr_it.global_infeas < termination.min_feasibility
obj_change = abs(prev_it.global_obj - curr_it.global_obj)
if obj_change < termination.min_improvement
if mpi.root
@info "Feasibility limit reached. Stopping."
end
return true
end
end
return false
end