My setting is
julia> Threads.nthreads()
4
I can’t figure out what is happening here. The behavior is
julia> parallel_CG!(B, θ, β, μ, ν)
┌ Info: before entering @threads
│ sub_j_vec =
│ 4-element Vector{Int64}:
│ 1
│ 4
│ 2
└ 3
┌ Info: inside @threads
└ j = 2
┌ Info: inside @threads
└ j = 1
┌ Info: inside @threads
└ j = 3
PS K:\uc24>
The Info
are some debugging message that I intend to let julia display. As you can see, the julia crashes so that the prompt reverts to my windows powershell.
The code to reproduce this is (copy-paste to REPL)
import JuMP, Gurobi; GRB_ENV = Gurobi.Env();
import JuMP.value as ı
import LinearAlgebra.⋅ as ⋅
import LinearAlgebra.norm as norm
import Random
function prev(t, d, T) return (n = t - d; n<1 ? T+n : n) end; # used in h[:U]
function gen_a_D_house(ID, Drng) return (
ID = ID,
CND = .5rand(1:7), # thermal conductance of the building wall
INR = rand(6:20), # thermal inertia of the building
COP = rand(2:.5:4), # how many Watts cooling power from 1-Watt e-power
Q_I = rand(3:9, T), # heat generated by indoor human activity, server etc.
Q_BUS = rand(25:35), # RateA of the cooling water/air pipeline
OH = rand(24:29), # the hottest indoor degree Celsius
OΔ = rand(4:9), # the coldest indoor degree Celsius = OH - OΔ
D = rand(Drng, T), # base demand
P_BUS = rand(12:33), # unidirectional due to no renewables
P_EV = [2, rand(3:7)], # MIN and MAX charging power
P_AC = rand(6:9), # AC's cooling power is limited
E_EV = rand(10:39),
U = [rand(1:4, rand(2:5)) for _ = 1:rand(1:4)] # an entry is a cycle vector of _an_ uninterruptible load
) end; function gen_a_G_house(ID, Drng) return (gen_a_D_house(ID, Drng)..., G = rand(3:17, T)) end;
function solve_compact_formulation()
x = similar(D, NamedTuple); # store the VariableRefs in NamedTuple's
model = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); # monolithic
JuMP.set_attribute(model, "MIPGap", 0);
JuMP.set_attribute(model, "MIPGapAbs", 0);
for (j, block) = enumerate(D) # operational detail for each individual block
if length(block) == 1
h = block[1]
bEV, pEV = JuMP.@variable(model, [1:T], Bin), JuMP.@variable(model, [1:T]); JuMP.@constraint(model, sum(pEV) ≥ h[:E_EV])
JuMP.@constraint(model, h[:P_EV][begin]bEV ≤ pEV); JuMP.@constraint(model, pEV ≤ h[:P_EV][end]bEV)
bU = JuMP.@variable(model, [t = 1:T, i = eachindex(h[:U])], Bin); JuMP.@constraint(model, sum(bU; dims = 1) .≥ true)
pU = JuMP.@expression(model, [t=1:T], sum(sum(bU[prev(t,φ-1,T), i]P for (φ,P) = enumerate(v)) for (i,v) = enumerate(h[:U]))) # Uninterruptible demand
q = JuMP.@variable(model, [1:T], lower_bound = 0, upper_bound = h[:Q_BUS]) # direct cooling power supply
pAC = JuMP.@variable(model, [1:T], lower_bound = 0, upper_bound = h[:P_AC]) # we consider only cooling mode in this instance
o = JuMP.@variable(model, [1:T], lower_bound = h[:OH]-h[:OΔ], upper_bound = h[:OH]) # indoor temperature (state variable)
JuMP.@constraint(model, [t=1:T], (O[t]-o[t])h[:CND] + h[:Q_I][t] -q[t] -h[:COP]pAC[t] == (o[t<T ? t+1 : 1]-o[t])h[:INR]) # dynamic equation
pBus = JuMP.@variable(model, [1:T], lower_bound = 0, upper_bound = h[:P_BUS]) # unidirectional for NO-DER household
JuMP.@constraint(model, pBus ≥ h[:D] + pEV + pU + pAC);
else
bLent, pLent = JuMP.@variable(model, [1:T], Bin), JuMP.@variable(model, [1:T])
bEV, pEV = JuMP.@variable(model, [1:T, 1:2], Bin), JuMP.@variable(model, [1:T, 1:2])
for (i, h) = enumerate(block) # EV's part
JuMP.@constraint(model, h[:P_EV][begin]view(bEV, :, i) ≤ view(pEV, :, i))
JuMP.@constraint(model, view(pEV, :, i) ≤ h[:P_EV][end]view(bEV, :, i))
if i == 1 # the household that has RES
JuMP.@constraint(model, (1 .- bLent)h[:P_EV][begin] ≤ pLent)
JuMP.@constraint(model, pLent ≤ (1 .- bLent)h[:P_EV][end])
JuMP.@constraint(model, h[:E_EV] ≤ sum(view(pEV, :, i)))
else
JuMP.@constraint(model, h[:E_EV] ≤ sum(view(pEV, :, i)) + sum(pLent))
end
JuMP.@constraint(model, view(bEV, :, i) ≤ bLent) # bLent = 0 means lending
end
bU = JuMP.@variable(model, [i = 1:2, j = eachindex(block[i][:U]), t = 1:T], Bin) # ⚠️ This construct is a bit involved
JuMP.@constraint(model, [i = 1:2, j = eachindex(block[i][:U])], sum(bU[i, j, :]) .≥ true)
pU = JuMP.@expression(model, [i = 1:2, t = 1:T], sum(sum(bU[i, j, prev(t,φ-1,T)]P for (φ,P) = enumerate(v)) for (j,v) = enumerate(block[i][:U])))
q = JuMP.@variable(model, [1:T, i = 1:2], lower_bound = 0, upper_bound = block[i][:Q_BUS])
pAC = JuMP.@variable(model, [1:T, i = 1:2], lower_bound = 0, upper_bound = block[i][:P_AC])
o = JuMP.@variable(model, [1:T, i = 1:2], lower_bound = block[i][:OH]-block[i][:OΔ], upper_bound = block[i][:OH])
JuMP.@constraint(model, [t = 1:T, i = 1:2],
(O[t]-o[t, i])block[i][:CND] + block[i][:Q_I][t] -q[t, i] -block[i][:COP]pAC[t, i] == (o[t<T ? t+1 : 1, i]-o[t, i])block[i][:INR]
); # dynamic equation
pBus = JuMP.@variable(model, [1:T, i = 1:2], lower_bound = i>1 ? 0 : -block[i][:P_BUS], upper_bound = block[i][:P_BUS])
for (i, h) = enumerate(block)
if i == 1
JuMP.@constraint(model, view(pBus, :, i) + h[:G] ≥ pLent + h[:D] + view(pEV, :, i) + view(pU, i, :) + view(pAC, :, i))
else
JuMP.@constraint(model, view(pBus, :, i) ≥ h[:D] + view(pEV, :, i) + view(pU, i, :) + view(pAC, :, i))
end
end
end
x[j] = (
q = q,
o = o,
pBus = pBus,
bEV = bEV,
pEV = pEV,
pAC = pAC,
bU = bU
); length(block) > 1 && (x[j] = (bLent = bLent, pLent = pLent, x[j]...))
end
JuMP.@expression(model, pAgr, sum(haskey(x[j], :bLent) ? vec(sum(x[j][:pBus]; dims = 2)) : x[j][:pBus] for j = eachindex(x)))
JuMP.@expression(model, qAgr, sum(haskey(x[j], :bLent) ? vec(sum(x[j][:q]; dims = 2)) : x[j][:q] for j = eachindex(x)))
JuMP.@constraint(model, β[t = 1:T], P_AGR ≥ pAgr[t]) # 🟡 Linking (note that we require positiveness thought not explicit here)
JuMP.@constraint(model, μ[t = 1:T], Q_AGR ≥ qAgr[t]) # 🟡 Linking
JuMP.@constraint(model, ν, E_AGR ≥ sum(qAgr)) # 🟡 Linking [ice-storage is limited]
JuMP.@objective(model, Min, MarketPrice ⋅ pAgr)
JuMP.optimize!(model); JuMP.assert_is_solved_and_feasible(model; allow_local = false)
println("compact one-shot solved in $(JuMP.solve_time(model)) seconds")
ı.(x[1][:bU]);
return an_UB = JuMP.objective_value(model) + 200
end;
function initialize_out(an_UB) # an_UB ⚠️ should be valid
model = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)) # outer in DantzigWolfe decomposition
JuMP.set_silent(model)
JuMP.@variable(model, β[1:T] ≥ 0) # e_power per t
JuMP.@variable(model, μ[1:T] ≥ 0) # q_power per t
JuMP.@variable(model, ν ≥ 0) # q_energy scalar
JuMP.@variable(model, θ[1:J]) # This is a concise formulation---the cut is info-intensive
JuMP.@expression(model, common, -sum(β)P_AGR -sum(μ)Q_AGR -(ν)E_AGR)
JuMP.@expression(model, out_obj_tbMax, common + sum(θ))
JuMP.@objective(model, Max, out_obj_tbMax)
JuMP.@constraint(model, out_obj_tbMax ≤ an_UB)
return model, θ, β, μ, ν
end; function initialize_inn(D)
inn = Vector{JuMP.Model}(undef, J); # the intrablock MIP subproblems
for (j, block) = enumerate(D)
model = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV))
JuMP.set_silent(model)
JuMP.set_attribute(model, "Threads", 1)
JuMP.set_attribute(model, "TimeLimit", 3)
JuMP.set_attribute(model, "MIPGap", 0)
JuMP.set_attribute(model, "MIPGapAbs", 0)
if length(block) == 1 # [pBus1; pBus2; pLent; vec(pEV); bLent; vec(bEV); vec(bDW)]
h = block[1]
JuMP.@variable(model, bEV[1:T], Bin); JuMP.@variable(model, pEV[1:T]); JuMP.@constraint(model, sum(pEV) ≥ h[:E_EV])
JuMP.@constraint(model, h[:P_EV][begin]bEV ≤ pEV); JuMP.@constraint(model, pEV ≤ h[:P_EV][end]bEV)
JuMP.@variable(model, bU[t = 1:T, i = eachindex(h[:U])], Bin); JuMP.@constraint(model, sum(bU; dims = 1) .≥ true)
JuMP.@expression(model, pU[t=1:T], sum(sum(bU[prev(t,φ-1,T), i]P for (φ,P) = enumerate(v)) for (i,v) = enumerate(h[:U]))) # Uninterruptible demand
JuMP.@variable(model, q[1:T], lower_bound = 0, upper_bound = h[:Q_BUS]) # direct cooling power supply
JuMP.@variable(model, pAC[1:T], lower_bound = 0, upper_bound = h[:P_AC]) # we consider only cooling mode in this instance
JuMP.@variable(model, o[1:T], lower_bound = h[:OH]-h[:OΔ], upper_bound = h[:OH]) # indoor temperature (state variable)
JuMP.@constraint(model, [t=1:T], (O[t]-o[t])h[:CND] + h[:Q_I][t] -q[t] -h[:COP]pAC[t] == (o[t<T ? t+1 : 1]-o[t])h[:INR]) # dynamic equation
JuMP.@variable(model, pBus[1:T], lower_bound = 0, upper_bound = h[:P_BUS]) # unidirectional for NO-DER household
JuMP.@constraint(model, pBus ≥ h[:D] + pEV + pU + pAC);
JuMP.@expression(model, prim_obj, MarketPrice ⋅ pBus) # some penalty terms still needed
else
JuMP.@variable(model, bLent[1:T], Bin); JuMP.@variable(model, pLent[1:T])
JuMP.@variable(model, bEV[1:T, 1:2], Bin); JuMP.@variable(model, pEV[1:T, 1:2])
for (i, h) = enumerate(block) # EV's part
JuMP.@constraint(model, h[:P_EV][begin]view(bEV, :, i) ≤ view(pEV, :, i))
JuMP.@constraint(model, view(pEV, :, i) ≤ h[:P_EV][end]view(bEV, :, i))
if i == 1 # the household that has RES
JuMP.@constraint(model, (1 .- bLent)h[:P_EV][begin] ≤ pLent)
JuMP.@constraint(model, pLent ≤ (1 .- bLent)h[:P_EV][end])
JuMP.@constraint(model, h[:E_EV] ≤ sum(view(pEV, :, i)))
else
JuMP.@constraint(model, h[:E_EV] ≤ sum(view(pEV, :, i)) + sum(pLent))
end
JuMP.@constraint(model, view(bEV, :, i) ≤ bLent) # bLent = 0 means lending
end
JuMP.@variable(model, bU[i = 1:2, j = eachindex(block[i][:U]), t = 1:T], Bin) # ⚠️ This construct is a bit involved
JuMP.@constraint(model, [i = 1:2, j = eachindex(block[i][:U])], sum(bU[i, j, :]) .≥ true)
JuMP.@expression(model, pU[i = 1:2, t = 1:T], sum(sum(bU[i, j, prev(t,φ-1,T)]P for (φ,P) = enumerate(v)) for (j,v) = enumerate(block[i][:U])))
JuMP.@variable(model, q[1:T, i = 1:2], lower_bound = 0, upper_bound = block[i][:Q_BUS])
JuMP.@variable(model, pAC[1:T, i = 1:2], lower_bound = 0, upper_bound = block[i][:P_AC])
JuMP.@variable(model, o[1:T, i = 1:2], lower_bound = block[i][:OH]-block[i][:OΔ], upper_bound = block[i][:OH])
JuMP.@constraint(model, [t = 1:T, i = 1:2],
(O[t]-o[t, i])block[i][:CND] + block[i][:Q_I][t] -q[t, i] -block[i][:COP]pAC[t, i] == (o[t<T ? t+1 : 1, i]-o[t, i])block[i][:INR]
); # dynamic equation
JuMP.@variable(model, pBus[1:T, i = 1:2], lower_bound = i>1 ? 0 : -block[i][:P_BUS], upper_bound = block[i][:P_BUS])
for (i, h) = enumerate(block)
if i == 1
JuMP.@constraint(model, view(pBus, :, i) + h[:G] ≥ pLent + h[:D] + view(pEV, :, i) + view(pU, i, :) + view(pAC, :, i))
else
JuMP.@constraint(model, view(pBus, :, i) ≥ h[:D] + view(pEV, :, i) + view(pU, i, :) + view(pAC, :, i))
end
end
JuMP.@expression(model, prim_obj, MarketPrice ⋅ sum(pBus; dims = 2)) # some penalty terms still needed
end; inn[j] = model # save
end # not include: penalty objective, non-block linking constr
return inn
end; function reset_mj_obj!(mj, Β, Μ, Ν)
pair = haskey(mj, :bLent)
pBus, q = mj[:pBus], mj[:q]
term_beta = JuMP.@expression(mj, pair ? sum((pBus[t,1] + pBus[t,2])Β[t] for t=1:T) : Β ⋅ pBus)
term_mu = JuMP.@expression(mj, pair ? sum((q[t,1] + q[t,2])Μ[t] for t=1:T) : Μ ⋅ q)
term_nu = JuMP.@expression(mj, sum(q)Ν)
JuMP.@objective(mj, Min, +(mj[:prim_obj], term_beta, term_mu, term_nu))
end; function build_con(j, mj, θ, β, μ, ν)
model = JuMP.owner_model(ν)
pair = haskey(mj, :bLent)
pBus, q = ı.(mj[:pBus]), ı.(mj[:q])
term_beta = JuMP.@expression(model, pair ? sum((pBus[t,1] + pBus[t,2])β[t] for t=1:T) : β ⋅ pBus)
term_mu = JuMP.@expression(model, pair ? sum((q[t,1] + q[t,2])μ[t] for t=1:T) : μ ⋅ q)
term_nu = JuMP.@expression(model, sum(q)ν)
cut_term = JuMP.@expression(model, +(ı(mj[:prim_obj]), term_beta, term_mu, term_nu))
return JuMP.@build_constraint(θ[j] ≤ cut_term)
end; function get_full_num_tuple(mj)
x = (
q = ı.(mj[:q]),
o = ı.(mj[:o]),
pBus = ı.(mj[:pBus]),
bEV = round.(Bool, ı.(mj[:bEV])),
pEV = ı.(mj[:pEV]),
pAC = ı.(mj[:pAC]),
bU = round.(Bool, ı.(mj[:bU]))
); haskey(mj, :bLent) && (x = (
bLent = round.(Bool, ı.(mj[:bLent])),
pLent = ı.(mj[:pLent]),
x...
)); return x
end; function batch(J, TH) return (J-1) ÷ TH + 1 end;
function parallel_CG!(B, θ, β, μ, ν)
COT = 1e-11
model = JuMP.owner_model(ν)
for ite = Base.OneTo(typemax(Int))
JuMP.optimize!(model); JuMP.assert_is_solved_and_feasible(model; allow_local = false, dual = true)
bound_of_out = JuMP.objective_bound(model) # Record
Β, Μ, Ν = ı.(β), ı.(μ), ı(ν) # ✂️
rand_j_vec, BATCH = Random.shuffle(Base.OneTo(J)), batch(J, TH)
cut_found_flag = false
for ba = Base.OneTo(BATCH) # do one partial parallel MIPs solving at an iteration
sub_j_vec = rand_j_vec[1+(ba-1)TH : min(J, (ba)TH)]
@info "before entering @threads" sub_j_vec
Threads.@threads for j = sub_j_vec # strictly This is NOT a loop
mj = inn[j]
reset_mj_obj!(mj, Β, Μ, Ν)
JuMP.optimize!(mj); # JuMP.assert_is_solved_and_feasible(mj; allow_local = false) # easy MIPs
@info "inside @threads" j
end
error("#1 can you see this error??")
Δ_vec = [ı(θ[j]) - JuMP.objective_value(inn[j]) for j = sub_j_vec]
Δ, ii = findmax(Δ_vec)
if Δ > COT
j = sub_j_vec[ii]
mj = inn[j]
intFullvertex = get_full_num_tuple(mj)
if !haskey(B[j], intFullvertex)
con = build_con(j, mj, θ, β, μ, ν)
cut = JuMP.add_constraint(model, con)
B[j][intFullvertex] = cut
@info "ite = $ite, cut's_maxvio = $Δ, out's bound = $bound_of_out"
cut_found_flag = true
break
end
end
end
cut_found_flag && continue
break
end
end;
seed = abs(rand(Int))
seed = 2004686255641684212
Random.seed!(seed)
TH = Threads.nthreads(:default)
T = 24; # global data
MarketPrice = [2, 1, 1, 1, 1, 1, 3, 5, 6, 7, 8, 9, 9, 8, 8, 9, 10, 11, 9, 6, 5, 3, 2, 2];
P_AGR = 47;
Q_AGR = 27;
E_AGR = 200;
O = [27.0, 27.2, 27.5, 27.8, 28.5, 30.0, 32.5, 35.0, 37.5, 39.5, 41.0, 42.0, 41.5, 40.5, 39.0, 37.0, 34.5, 32.0, 30.0, 29.0, 28.0, 27.5, 27.2, 27.0]; # global data: outdoor temperature
D = Vector{Vector{NamedTuple}}(undef, 4); # generate all the households
D[1] = [gen_a_G_house(1, 1:3), gen_a_D_house(2, 0:5)];
D[2] = [gen_a_D_house(3, 0:5)];
D[3] = [gen_a_D_house(4, 0:6)];
D[4] = [gen_a_D_house(5, 0:5)];
J = length(D);
an_UB = solve_compact_formulation();
model, θ, β, μ, ν = initialize_out(an_UB);
inn = initialize_inn(D); # haskey(inn[1], :bLent) == true
# my_lock = Threads.ReentrantLock();
B = [Dict{NamedTuple, JuMP.ConstraintRef}() for j = 1:J]; # a vec of Bijections::NamedTuple
parallel_CG!(B, θ, β, μ, ν)