Skip to content

Commit 2e64d4d

Browse files
author
Rico Schrage
committed
Using an abstract layer for admm algorithms now. Implemented sharing as variant besides general consensus.
1 parent e717530 commit 2e64d4d

13 files changed

Lines changed: 673 additions & 277 deletions

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ version = "0.1.0"
88
AutoHashEquals = "15f4f7f2-30c1-5605-9d31-71845cf9641f"
99
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
1010
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
11+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1112
Mango = "5e49fdec-d473-4d14-b295-7bff2fcf1925"
1213
OSQP = "ab2f91bb-94b4-55e3-9ba0-7f65df51de79"
1314
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
@@ -16,6 +17,7 @@ Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
1617
AutoHashEquals = "2.2.0"
1718
Distributions = "0.25.115"
1819
JuMP = "1.25.0"
20+
LinearAlgebra = "1.11.0"
1921
Mango = "^0.4"
2022
OSQP = "0.8.1"
2123
Revise = "3.6.4"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# Distributed Optimization for Julia
33

4-
The package DistributedOptimization.jl (DO) aims to provide a collection of distributed algorithms. The algorithms are implemented without considering one special communication technique or package. DO provides abstract types and function interfaces to implement so-called carriers, which are able to execute the distributed algorithms asynchronous. All algorithms can also be used without carrier using fitting @spawn or @async statements.
4+
The package DistributedOptimization.jl (DO) aims to provide a collection of distributed optimization algorithms. The algorithms are implemented without considering one special communication technique or package. DO provides abstract types and function interfaces to implement so-called carriers, which are able to execute the distributed algorithms asynchronous. All algorithms can also be used without carrier using fitting @spawn or @async statements.
55

66
Currently there are two tested algorithms:
77
* ADMM multi-value consensus such that the sum of all resp values equals a target vector
@@ -10,3 +10,4 @@ Currently there are two tested algorithms:
1010
There is one carrier implemented:
1111
* Mango.jl, agent framework for the simulation of distributed systems, DO provides roles to which the specific algorithms can be assigned to
1212

13+
Note that the package is highly work in progress.

src/DistributedOptimization.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ include("carrier/core.jl")
55
include("algorithm/core.jl")
66
include("algorithm/heuristic/cohda/core.jl")
77
include("algorithm/heuristic/cohda/decider.jl")
8-
include("algorithm/admm/flex_admm.jl")
8+
9+
include("algorithm/admm/core.jl")
10+
include("algorithm/admm/flex_actor.jl")
11+
include("algorithm/admm/consensus_admm.jl")
12+
include("algorithm/admm/sharing_admm.jl")
13+
914
include("carrier/mango.jl")
1015

1116
end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export create_consensus_target_reach_admm_coordinator, ADMMConsensusGlobalActor, create_admm_start_with_target
2+
3+
4+
@kwdef struct ADMMConsensusGlobalActor <: ADMMGlobalActor
5+
α::Int = 100
6+
end
7+
8+
function z_update(actor::ADMMConsensusGlobalActor, input::Vector{<:Real}, x, u, z, ρ, N)
9+
m = length(z)
10+
S = zeros(m)
11+
for i in 1:N
12+
S .+= x[i] .+ u[i]
13+
end
14+
δ = (input .- S) ./ (N + actor.α/ρ)
15+
for i in 1:N
16+
z[i] = x[i] .+ u[i] .+ δ
17+
end
18+
return z
19+
end
20+
21+
function u_update(actor::ADMMConsensusGlobalActor, x, u, z, ρ, N)
22+
return u + x - z
23+
end
24+
25+
function init_z(actor::ADMMConsensusGlobalActor, n::Int, m::Int)
26+
return [ones(m) for _ in 1:n]
27+
end
28+
29+
function init_u(actor::ADMMConsensusGlobalActor, n::Int, m::Int)
30+
return [zeros(m) for _ in 1:n]
31+
end
32+
33+
function actor_correction(actor::ADMMConsensusGlobalActor, x, z, u, i)
34+
return - z[i] + u[i]
35+
end
36+
37+
function primal_residual(actor::ADMMConsensusGlobalActor, x, z)
38+
return maximum(norm.(x .- z))
39+
end
40+
41+
function create_consensus_target_reach_admm_coordinator()
42+
return ADMMGenericCoordinator(global_actor=ADMMConsensusGlobalActor())
43+
end
44+
45+
function create_admm_start_with_target(target::Vector{<:Real})
46+
return ADMMStart(target, length(target))
47+
end

src/algorithm/admm/core.jl

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
export ADMMStart, ADMMAnswer, ADMMAnswer, ADMMGlobalActor, ADMMGlobalObjective, ADMMGenericCoordinator
2+
3+
struct ADMMStart
4+
data::Any
5+
solution_length::Int
6+
end
7+
8+
struct ADMMMessage
9+
v::Vector{Float64}
10+
ρ::Float64
11+
end
12+
13+
struct ADMMAnswer
14+
x::Vector{Float64}
15+
end
16+
17+
abstract type ADMMGlobalActor end
18+
19+
function z_update(actor::ADMMGlobalActor, x, u, z, ρ, N)
20+
throw("NotImplemented")
21+
end
22+
23+
function u_update(actor::ADMMGlobalActor, x, u, z, ρ, N)
24+
throw("NotImplemented")
25+
end
26+
27+
function init_z(actor::ADMMGlobalActor, n::Int, m::Int)
28+
throw("NotImplemented")
29+
end
30+
31+
function init_u(actor::ADMMGlobalActor, n::Int, m::Int)
32+
throw("NotImplemented")
33+
end
34+
35+
function actor_correction(actor::ADMMGlobalActor, x, z, u, i)
36+
throw("NotImplemented")
37+
end
38+
39+
function primal_residual(actor::ADMMGlobalActor, x, z)
40+
throw("NotImplemented")
41+
end
42+
43+
abstract type ADMMGlobalObjective end
44+
45+
function objective(global_objective::ADMMGlobalObjective, x, u, z, N)
46+
throw("NotImplemented")
47+
end
48+
49+
@kwdef struct ADMMGenericCoordinator <: Coordinator
50+
global_actor::ADMMGlobalActor
51+
ρ::Float64 = 1.0
52+
max_iters::Int64 = 100
53+
slack_penalty::Int64 = 100
54+
abs_tol::Float64 = 1e-4
55+
rel_tol::Float64 = 1e-3
56+
μ::Real = 10
57+
τ::Real = 2
58+
end
59+
60+
# ADMM solver
61+
function _start_coordinator(admm::ADMMGenericCoordinator, carrier::Carrier, input::Any, m::Int)
62+
ρ = admm.ρ
63+
max_iters = admm.max_iters
64+
abs_tol = admm.abs_tol
65+
rel_tol = admm.rel_tol
66+
n = length(others(carrier, "coordinator"))
67+
μ = admm.μ
68+
τ = admm.τ
69+
70+
# Initialize
71+
x = [zeros(m) for i in 1:n]
72+
z = init_z(admm.global_actor, n, m)
73+
u = init_u(admm.global_actor, n, m)
74+
75+
for k in 1:max_iters
76+
# 1. Local x-updates (in parallel)
77+
awaitables = []
78+
# send all async and get awaitable
79+
for (i,addr) in enumerate(others(carrier, "c"))
80+
push!(awaitables, send_awaitable(carrier, ADMMMessage(actor_correction(admm.global_actor, x, z, u, i), ρ), addr))
81+
end
82+
# await all awaitables and update x
83+
for (i,awaitable) in enumerate(awaitables)
84+
x[i] = wait(carrier, awaitable).x
85+
end
86+
87+
# 2. Global z-update
88+
z_old = deepcopy(z)
89+
90+
z = z_update(admm.global_actor, input, x, u, z, ρ, n)
91+
u = u_update(admm.global_actor, x, u, z, ρ, n)
92+
93+
# 4. Check convergence
94+
# primal residual: max over i of ||x_i - z_i||
95+
r_norm = primal_residual(admm.global_actor, x, z)
96+
# dual residual: ρ * max over i of ||z_i - z_old_i||
97+
s_norm = ρ * maximum(norm.(z .- z_old))
98+
# tolerances
99+
ϵ_pri = sqrt(m*n)*abs_tol + rel_tol*max(maximum(norm.(x)), maximum(norm.(z)))
100+
ϵ_dual = sqrt(m*n)*abs_tol + rel_tol*maximum(norm.(u))
101+
if r_norm < ϵ_pri && s_norm < ϵ_dual
102+
@warn "Converged in $k iterations."
103+
break
104+
end
105+
106+
# Varying penalty paramter according to B. S. He, H. Yang, and S. L. Wang, “Alternating direction method with self
107+
# adaptive penalty parameters for monotone variational inequalities,”
108+
if r_norm > μ * s_norm
109+
ρ = ρ * τ
110+
elseif s_norm > μ * r_norm
111+
ρ = ρ / τ
112+
end
113+
114+
if k == max_iters
115+
@warn "Reached max iterations ($max_iters) without full convergence."
116+
throw("ADMM not converged $x, $u")
117+
end
118+
end
119+
return x, z, u
120+
end
121+
122+
function start_optimization(coordinator::ADMMGenericCoordinator, carrier::Carrier, start_data::ADMMStart, meta::Any)
123+
x,_,_ = _start_coordinator(coordinator, carrier, start_data.data, start_data.solution_length)
124+
return x
125+
end

src/algorithm/admm/flex_actor.jl

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
export ADMMFlexActor, create_admm_flex_actor_one_to_many, result
2+
3+
using JuMP
4+
using OSQP
5+
using LinearAlgebra
6+
7+
mutable struct ADMMFlexActor <: DistributedAlgorithm
8+
l::Vector{Float64} # lower bounds
9+
u::Vector{Float64} # upper bounds
10+
C::Matrix{Float64} # coupling matrix
11+
d::Vector{Float64} # coupling RHS
12+
S::Vector{Float64} # prio penalty per sector
13+
x::Vector{Float64} # intermediate result/result
14+
ADMMFlexActor(l::Vector{Float64},
15+
u::Vector{Float64},
16+
C::Matrix{Float64},
17+
d::Vector{Float64},
18+
S::Vector{Float64}) = new(l, u, C, d, S, Vector{Float64}())
19+
end
20+
21+
function _create_C_and_d(u::Vector{<:Real})
22+
m = length(u)
23+
R = 1 + 2*(m-1)
24+
C = zeros(eltype(u), R, m)
25+
d = zeros(eltype(u), R)
26+
27+
for i in 1:length(u)
28+
C[1, i] = u[i] < 0 ? -1 : 1
29+
end
30+
d[1] = sum(abs.(u))
31+
32+
for j in 1:(m-1)
33+
34+
r1 = 1 + 2*(j-1) + 1
35+
r2 = r1 + 1
36+
37+
C[r1, j] = u[j] == 0 || u[m] == 0 ? 0 : 1/u[j]
38+
C[r1, m] = u[j] == 0 || u[m] == 0 ? 0 : -1/u[m]
39+
40+
C[r2, j] = u[j] == 0 || u[m] == 0 ? 0 : -1/u[j]
41+
C[r2, m] = u[j] == 0 || u[m] == 0 ? 0 : 1/u[m]
42+
end
43+
44+
return C, d
45+
end
46+
47+
function create_admm_flex_actor_one_to_many(in_capacity::Real, η::Vector{Float64}, S::Union{Nothing,Vector{<:Real}}=nothing)
48+
tech_capacity = in_capacity .* η
49+
50+
if isnothing(S)
51+
S = zeros(length(η))
52+
end
53+
l = min.(zeros(length(tech_capacity)), tech_capacity)
54+
u = max.(tech_capacity, zeros(length(tech_capacity)))
55+
C, d = _create_C_and_d(tech_capacity)
56+
57+
return ADMMFlexActor(l, u, C, d, S)
58+
end
59+
60+
function result(actor::ADMMFlexActor)
61+
return actor.x
62+
end
63+
64+
# Solve the projection / local update via QP: minimize 1/2||x - v||^2 s.t. l <= x <= u, Cx <= d
65+
function _local_update(actor::ADMMFlexActor, v::Vector{Float64}, ρ::Float64)
66+
m = length(v)
67+
model = Model(OSQP.Optimizer)
68+
set_silent(model)
69+
70+
@variable(model, x[1:m])
71+
72+
# admm objective: (ρ/2)||x - v||^2 + S_i·x
73+
# Note that v = - (correction) in the implementation as a result the equation is based on (ρ/2)||x + v||^2 + S_i·x
74+
# transform to standard form Hx^2 - hx
75+
# in some cases about 100-1000x faster
76+
H = Diagonal(ones(m) * ρ)
77+
h = ρ*v
78+
79+
# + priority cost term: S_i·x
80+
@objective(model, Min, 0.5 * sum(H[i,i]*x[i]^2 for i=1:m) + sum(h[i]*x[i] for i=1:m) + sum(actor.S[i] * x[i] for i in 1:m))
81+
#@objective(model, Min, (ρ/2)*sum((x[i] + v[i])^2 + x[i]*actor.S[i] for i in 1:m))
82+
83+
# box constraints
84+
@constraint(model, [i=1:m], actor.l[i] <= x[i] <= actor.u[i])
85+
86+
# coupling constraints
87+
@constraint(model, actor.C * x .<= actor.d)
88+
89+
optimize!(model)
90+
91+
return value.(x)
92+
end
93+
94+
function on_exchange_message(actor::ADMMFlexActor, carrier::Carrier, message_data::ADMMMessage, meta::Any)
95+
actor.x = _local_update(actor, message_data.v, message_data.ρ)
96+
97+
reply_to_other(carrier, ADMMAnswer(actor.x), meta)
98+
end

0 commit comments

Comments
 (0)