|
|
# 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
|