Skip to content

Commit

Permalink
Add basic support for PST in DC OPF models (#875)
Browse files Browse the repository at this point in the history
* add basic support for pst in dc opf models, closes #543
* drop unstable pf test in windows and julia v1.9
  • Loading branch information
ccoffrin committed Aug 11, 2023
1 parent 13c49b7 commit 9a0a4db
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PowerModels.jl Change Log
=========================

### Staged
- Add support for ACP and DCP formulation in OPF with PST variables (#543,#875)
- Fix implementation of `calc_theta_delta_bounds` when conductor parameter is used (#870)

### v0.19.9
Expand Down
36 changes: 36 additions & 0 deletions src/core/constraint_template.jl
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,42 @@ function constraint_ohms_y_oltc_pst_to(pm::AbstractPowerModel, i::Int; nw::Int=n
end



""
function constraint_ohms_y_pst_from(pm::AbstractPowerModel, i::Int; nw::Int=nw_id_default)
branch = ref(pm, nw, :branch, i)
f_bus = branch["f_bus"]
t_bus = branch["t_bus"]
f_idx = (i, f_bus, t_bus)
t_idx = (i, t_bus, f_bus)

g, b = calc_branch_y(branch)
g_fr = branch["g_fr"]
b_fr = branch["b_fr"]
tm = branch["tap"]

constraint_ohms_y_pst_from(pm, nw, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tm)
end


""
function constraint_ohms_y_pst_to(pm::AbstractPowerModel, i::Int; nw::Int=nw_id_default)
branch = ref(pm, nw, :branch, i)
f_bus = branch["f_bus"]
t_bus = branch["t_bus"]
f_idx = (i, f_bus, t_bus)
t_idx = (i, t_bus, f_bus)

g, b = calc_branch_y(branch)
g_to = branch["g_to"]
b_to = branch["b_to"]
tm = branch["tap"]

constraint_ohms_y_pst_to(pm, nw, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tm)
end



""
function constraint_voltage_drop(pm::AbstractPowerModel, i::Int; nw::Int=nw_id_default)
branch = ref(pm, nw, :branch, i)
Expand Down
30 changes: 30 additions & 0 deletions src/form/acp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,36 @@ function constraint_ohms_y_oltc_pst_to(pm::AbstractACPModel, n::Int, f_bus, t_bu
JuMP.@NLconstraint(pm.model, q_to == -(b+b_to)*vm_to^2 - -b/tm*(vm_to*vm_fr*cos(va_to-va_fr+ta)) + -g/tm*(vm_to*vm_fr*sin(va_to-va_fr+ta)) )
end


""
function constraint_ohms_y_pst_from(pm::AbstractACPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tm)
p_fr = var(pm, n, :p, f_idx)
q_fr = var(pm, n, :q, f_idx)
vm_fr = var(pm, n, :vm, f_bus)
vm_to = var(pm, n, :vm, t_bus)
va_fr = var(pm, n, :va, f_bus)
va_to = var(pm, n, :va, t_bus)
ta = var(pm, n, :ta, f_idx[1])

JuMP.@NLconstraint(pm.model, p_fr == (g+g_fr)/tm^2*vm_fr^2 + (-g)/tm*(vm_fr*vm_to*cos(va_fr-va_to-ta)) + (-b)/tm*(vm_fr*vm_to*sin(va_fr-va_to-ta)) )
JuMP.@NLconstraint(pm.model, q_fr == -(b+b_fr)/tm^2*vm_fr^2 - (-b)/tm*(vm_fr*vm_to*cos(va_fr-va_to-ta)) + (-g)/tm*(vm_fr*vm_to*sin(va_fr-va_to-ta)) )
end

""
function constraint_ohms_y_pst_to(pm::AbstractACPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tm)
p_to = var(pm, n, :p, t_idx)
q_to = var(pm, n, :q, t_idx)
vm_fr = var(pm, n, :vm, f_bus)
vm_to = var(pm, n, :vm, t_bus)
va_fr = var(pm, n, :va, f_bus)
va_to = var(pm, n, :va, t_bus)
ta = var(pm, n, :ta, f_idx[1])

JuMP.@NLconstraint(pm.model, p_to == (g+g_to)*vm_to^2 + -g/tm*(vm_to*vm_fr*cos(va_to-va_fr+ta)) + -b/tm*(vm_to*vm_fr*sin(va_to-va_fr+ta)) )
JuMP.@NLconstraint(pm.model, q_to == -(b+b_to)*vm_to^2 - -b/tm*(vm_to*vm_fr*cos(va_to-va_fr+ta)) + -g/tm*(vm_to*vm_fr*sin(va_to-va_fr+ta)) )
end


"`angmin <= z_branch[i]*(t[f_bus] - t[t_bus]) <= angmax`"
function constraint_voltage_angle_difference_on_off(pm::AbstractACPModel, n::Int, f_idx, angmin, angmax, vad_min, vad_max)
i, f_bus, t_bus = f_idx
Expand Down
23 changes: 23 additions & 0 deletions src/form/dcp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function variable_bus_voltage_magnitude(pm::AbstractDCPModel; nw::Int=nw_id_defa
report && sol_component_fixed(pm, nw, :bus, :vm, ids(pm, nw, :bus), 1.0)
end


""
function sol_data_model!(pm::AbstractDCPModel, solution::Dict)
# nothing to do, this is in the data model space by default
Expand Down Expand Up @@ -134,6 +135,28 @@ function constraint_ohms_yt_from(pm::AbstractDCMPPModel, n::Int, f_bus, t_bus, f
JuMP.@constraint(pm.model, p_fr == (va_fr - va_to - ta)/(x*tm))
end

""
function constraint_ohms_y_pst_from(pm::AbstractDCPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tm)
p_fr = var(pm, n, :p, f_idx)
va_fr = var(pm, n, :va, f_bus)
va_to = var(pm, n, :va, t_bus)
ta = var(pm, n, :ta, f_idx[1])

JuMP.@constraint(pm.model, p_fr == -b*(va_fr - va_to - ta))
end

""
function constraint_ohms_y_pst_to(pm::AbstractDCPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tm)
p_to = var(pm, n, :p, t_idx)
va_fr = var(pm, n, :va, f_bus)
va_to = var(pm, n, :va, t_bus)
ta = var(pm, n, :ta, f_idx[1])

JuMP.@constraint(pm.model, p_to == -b*(va_to - va_fr + ta))
end



""
function constraint_switch_state_closed(pm::AbstractDCPModel, n::Int, f_bus, t_bus)
va_fr = var(pm, n, :va, f_bus)
Expand Down
44 changes: 43 additions & 1 deletion src/prob/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,49 @@ function _build_opf_strg_mi(pm::AbstractPowerModel)
end


"opf with tap magnitude and angle as optimization variables"
"opf with transformer angles as optimization variables"
function _solve_opf_pst(file, model_type::Type, optimizer; kwargs...)
return solve_model(file, model_type, optimizer, _build_opf_pst; kwargs...)
end

""
function _build_opf_pst(pm::AbstractPowerModel)
variable_bus_voltage(pm)
variable_gen_power(pm)

variable_branch_transform_angle(pm)
variable_branch_power(pm)
variable_dcline_power(pm)

objective_min_fuel_and_flow_cost(pm)

constraint_model_voltage(pm)

for i in ids(pm, :ref_buses)
constraint_theta_ref(pm, i)
end

for i in ids(pm, :bus)
constraint_power_balance(pm, i)
end

for i in ids(pm, :branch)
constraint_ohms_y_pst_from(pm, i)
constraint_ohms_y_pst_to(pm, i)

constraint_voltage_angle_difference(pm, i)

constraint_thermal_limit_from(pm, i)
constraint_thermal_limit_to(pm, i)
end

for i in ids(pm, :dcline)
constraint_dcline_power_losses(pm, i)
end
end


"opf with transformer tap magnitude and angle as optimization variables"
function _solve_opf_oltc_pst(file, model_type::Type, optimizer; kwargs...)
return solve_model(file, model_type, optimizer, _build_opf_oltc_pst; kwargs...)
end
Expand Down
81 changes: 79 additions & 2 deletions test/opf-var.jl
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,82 @@ end
end
end


@testset "test opf with optimization of pst" begin

@testset "test ac polar opf" begin

@testset "3-bus case with optimal phase shifting" begin
file = "../test/data/matpower/case3_oltc_pst.m"
data = PowerModels.parse_file(file)
result = PowerModels._solve_opf_pst(data, ACPPowerModel, nlp_solver)

@test result["termination_status"] == LOCALLY_SOLVED
@test isapprox(result["objective"], 5755.3; atol = 1e0)

@test haskey(result["solution"]["branch"]["1"], "ta")

@test isapprox(result["solution"]["branch"]["1"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["2"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["3"]["ta"], 15.0/180*pi; atol = 1e-1)
end

@testset "3-bus case with optimal phase shifting with equal lb/ub" begin
file = "../test/data/matpower/case3_oltc_pst.m"
data = PowerModels.parse_file(file)
for (i, branch) in data["branch"]
branch["ta_min"] = branch["shift"]
branch["ta_max"] = branch["shift"]
end
result = PowerModels._solve_opf_pst(data, ACPPowerModel, nlp_solver)

@test result["termination_status"] == LOCALLY_SOLVED
@test isapprox(result["objective"], 5820.1; atol = 1e0)

@test isapprox(result["solution"]["branch"]["1"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["2"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["3"]["ta"], 5.0/180*pi; atol = 1e-1)
end
end


@testset "test dc polar opf" begin
@testset "3-bus case with optimal phase shifting" begin
file = "../test/data/matpower/case3_oltc_pst.m"
data = PowerModels.parse_file(file)
result = PowerModels._solve_opf_pst(data, DCPPowerModel, milp_solver)

@test result["termination_status"] == OPTIMAL
@test isapprox(result["objective"], 5639.0; atol = 1e0)

@test haskey(result["solution"]["branch"]["1"], "ta")

@test isapprox(result["solution"]["branch"]["1"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["2"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["3"]["ta"], 15.0/180*pi; atol = 1e-1)
end

@testset "3-bus case with optimal phase shifting with equal lb/ub" begin
file = "../test/data/matpower/case3_oltc_pst.m"
data = PowerModels.parse_file(file)
for (i, branch) in data["branch"]
branch["ta_min"] = branch["shift"]
branch["ta_max"] = branch["shift"]
end
result = PowerModels._solve_opf_pst(data, DCPPowerModel, milp_solver)

@test result["termination_status"] == OPTIMAL
@test isapprox(result["objective"], 5698.1; atol = 1e0)

@test isapprox(result["solution"]["branch"]["1"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["2"]["ta"], 0.000; atol = 1e-3)
@test isapprox(result["solution"]["branch"]["3"]["ta"], 5.0/180*pi; atol = 1e-1)
end
end

end


@testset "test opf with optimization of oltc and pst" begin

@testset "test ac polar opf" begin
Expand All @@ -899,7 +975,7 @@ end
@test isapprox(result["objective"], 5820.1; atol = 1e0)
end

@testset "3-bus case with optimal phase shifting / tap changing" begin
@testset "3-bus case with optimal phase shifting and tap changing" begin
file = "../test/data/matpower/case3_oltc_pst.m"
data = PowerModels.parse_file(file)
result = PowerModels._solve_opf_oltc_pst(data, ACPPowerModel, nlp_solver)
Expand All @@ -919,7 +995,7 @@ end
end


@testset "3-bus case with optimal phase shifting / tap changing with equal lb/ub" begin
@testset "3-bus case with optimal phase shifting and tap changing with equal lb/ub" begin
file = "../test/data/matpower/case3_oltc_pst.m"
data = PowerModels.parse_file(file)
for (i, branch) in data["branch"]
Expand All @@ -941,4 +1017,5 @@ end
@test isapprox(result["solution"]["branch"]["3"]["ta"], 5.0/180*pi; atol = 1e-1)
end
end

end
11 changes: 6 additions & 5 deletions test/pf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,13 @@ end
@test isapprox(result["solution"]["dcline"]["1"]["pf"], 0.10; atol = 1e-4)
@test isapprox(result["solution"]["dcline"]["1"]["pt"], -0.10; atol = 1e-4)
end
@testset "5-bus asymmetric case" begin
result = run_pf_bf("../test/data/matpower/case5_asym.m", SOCBFPowerModel, nlp_solver)
# removed due to windows instability in Julia v1.9
# @testset "5-bus asymmetric case" begin
# result = run_pf_bf("../test/data/matpower/case5_asym.m", SOCBFPowerModel, nlp_solver)

@test result["termination_status"] == LOCALLY_SOLVED
@test isapprox(result["objective"], 0; atol = 1e-2)
end
# @test result["termination_status"] == LOCALLY_SOLVED
# @test isapprox(result["objective"], 0; atol = 1e-2)
# end
@testset "5-bus case with hvdc line" begin
result = run_pf_bf("../test/data/matpower/case5_dc.m", SOCBFPowerModel, nlp_solver)

Expand Down

0 comments on commit 9a0a4db

Please sign in to comment.