Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for PST in DC OPF models #875

Merged
merged 4 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading