Skip to content

Commit

Permalink
ADD: UBF Matrix Power Variables for Switches (#424)
Browse files Browse the repository at this point in the history
This adds matrix P and Q variables for switches to AbstractUBFModels.

Closes #423
Resolves #325
  • Loading branch information
pseudocubic authored Jan 20, 2023
1 parent ea83507 commit f5c785e
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## staged

- none
- Added UBF matrix power variables for switches [#423](https://github.com/lanl-ansi/PowerModelsDistribution.jl/issues/423)

## v0.14.5

Expand Down
64 changes: 64 additions & 0 deletions src/form/bf_mx.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,70 @@ function variable_mc_branch_power(pm::AbstractUBFModels; nw::Int=nw_id_default,
end


"matrix power variables for switches"
function variable_mc_switch_power(pm::AbstractUBFModels; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true)
# calculate S bound
switch_arcs = Vector{Tuple{Int,Int,Int}}(ref(pm, nw, :arcs_switch))
connections = Dict{Tuple{Int,Int,Int},Vector{Int}}((l,i,j) => connections for (bus,entry) in ref(pm, nw, :bus_arcs_conns_switch) for ((l,i,j), connections) in entry)

if bounded
bound = Dict{eltype(switch_arcs), Matrix{Real}}()
for (l, switch) in ref(pm, nw, :switch)
bus_fr = ref(pm, nw, :bus, switch["f_bus"])
bus_to = ref(pm, nw, :bus, switch["t_bus"])

smax_fr = _calc_branch_power_max(switch, bus_fr)
smax_to = _calc_branch_power_max(switch, bus_to)
cmax_fr, cmax_to = _calc_branch_current_max_frto(switch, bus_fr, bus_to)

tuple_fr = (l, bus_fr["index"], bus_to["index"])
tuple_to = (l, bus_to["index"], bus_fr["index"])

bound[tuple_fr] = bus_fr["vmax"][[findfirst(isequal(c), bus_fr["terminals"]) for c in switch["f_connections"]]].*cmax_fr'
bound[tuple_to] = bus_to["vmax"][[findfirst(isequal(c), bus_to["terminals"]) for c in switch["t_connections"]]].*cmax_to'

for (idx, (fc,tc)) in enumerate(zip(switch["f_connections"], switch["t_connections"]))
bound[tuple_fr][idx,idx] = smax_fr[idx]
bound[tuple_to][idx,idx] = smax_to[idx]
end
end
# create matrix variables
(P,Q) = variable_mx_complex(pm.model, switch_arcs, connections, connections; symm_bound=bound, name=("Psw", "Qsw"), prefix="$nw")
else
(P,Q) = variable_mx_complex(pm.model, switch_arcs, connections, connections; name=("Psw", "Qsw"), prefix="$nw")
end

# this explicit type erasure is necessary
P_expr = merge(
Dict{Any,Any}( (l,i,j) => P[(l,i,j)] for (l,i,j) in ref(pm, nw, :arcs_switch_from) ),
Dict( (l,j,i) => -1.0.*P[(l,i,j)] for (l,i,j) in ref(pm, nw, :arcs_switch_from))
)
Q_expr = merge(
Dict{Any,Any}( (l,i,j) => Q[(l,i,j)] for (l,i,j) in ref(pm, nw, :arcs_switch_from) ),
Dict( (l,j,i) => -1.0*Q[(l,i,j)] for (l,i,j) in ref(pm, nw, :arcs_switch_from))
)

# This is needed to get around error: "unexpected affine expression in nlconstraint"
(P_aux,Q_aux) = variable_mx_complex(pm.model, switch_arcs, connections, connections; name=("Psw_aux", "Qsw_aux"), prefix="$nw")
for (l,i,j) in switch_arcs
JuMP.@constraint(pm.model, P_expr[(l,i,j)] .== P_aux[(l,i,j)])
JuMP.@constraint(pm.model, Q_expr[(l,i,j)] .== Q_aux[(l,i,j)])
end

# save reference
var(pm, nw)[:Psw] = P_aux
var(pm, nw)[:Qsw] = Q_aux

var(pm, nw)[:psw] = Dict([(id,LinearAlgebra.diag(P_aux[id])) for id in switch_arcs])
var(pm, nw)[:qsw] = Dict([(id,LinearAlgebra.diag(Q_aux[id])) for id in switch_arcs])

report && _IM.sol_component_value_edge(pm, pmd_it_sym, nw, :switch, :Pf, :Pt, ref(pm, nw, :arcs_switch_from), ref(pm, nw, :arcs_switch_to), P_expr)
report && _IM.sol_component_value_edge(pm, pmd_it_sym, nw, :switch, :Qf, :Qt, ref(pm, nw, :arcs_switch_from), ref(pm, nw, :arcs_switch_to), Q_expr)
report && _IM.sol_component_value_edge(pm, pmd_it_sym, nw, :switch, :pf, :pt, ref(pm, nw, :arcs_switch_from), ref(pm, nw, :arcs_switch_to), Dict([(id,LinearAlgebra.diag(P_expr[id])) for id in switch_arcs]))
report && _IM.sol_component_value_edge(pm, pmd_it_sym, nw, :switch, :qf, :qt, ref(pm, nw, :arcs_switch_from), ref(pm, nw, :arcs_switch_to), Dict([(id,LinearAlgebra.diag(Q_expr[id])) for id in switch_arcs]))
end


"defines matrix transformer power variables for the unbalanced branch flow models"
function variable_mc_transformer_power(pm::AbstractUBFModels; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true)
transformer_arcs = Vector{Tuple{Int,Int,Int}}(ref(pm, nw, :arcs_transformer))
Expand Down
51 changes: 51 additions & 0 deletions test/opf_bf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,56 @@
@test isapprox(result["objective"], 21.17; atol = 5e-2)
end
end

@testset "test ubf relaxations with with switches" begin
data = deepcopy(case3_balanced_switch)
make_lossless!(data; exclude=["line", "linecode"])
remove_line_limits!(data)
apply_voltage_bounds!(data; vm_lb=0.9, vm_ub=1.1)

data["settings"]["sbase_default"] = 1.0

data["generator"] = Dict{String,Any}(
"1" => Dict{String,Any}(
"bus" => "primary",
"connections" => [1, 2, 3, 4],
"cost_pg_parameters" => [0.0, 1200.0, 0.0],
"qg_lb" => fill(0.0, 3),
"qg_ub" => fill(0.0, 3),
"pg_ub" => fill(10, 3),
"pg_lb" => fill(0, 3),
"configuration" => WYE,
"status" => ENABLED
)
)

merge!(data["voltage_source"]["source"], Dict{String,Any}(
"cost_pg_parameters" => [0.0, 1000.0, 0.0],
"pg_lb" => fill( 0.0, 3),
"pg_ub" => fill( 10.0, 3),
"qg_lb" => fill(-10.0, 3),
"qg_ub" => fill( 10.0, 3),
))

for (_,line) in data["line"]
line["sm_ub"] = fill(10.0, 3)
end

@testset "test SOCNLPUBF opf with switches" begin
result = solve_mc_opf(data, SOCNLPUBFPowerModel, ipopt_solver)

@test result["termination_status"] == LOCALLY_SOLVED || result["termination_status"] == ALMOST_LOCALLY_SOLVED
@test all(isapprox.(result["solution"]["switch"]["ohline"]["pf"], [6.0, 6.0, 6.0]; atol=1e-1))
@test isapprox(result["objective"], 18.1824; atol=2e-1)
end

@testset "test SOCConicUBF opf with switches" begin
result = solve_mc_opf(data, SOCConicUBFPowerModel, scs_solver)

@test result["termination_status"] == OPTIMAL || result["termination_status"] == ALMOST_OPTIMAL
@test all(isapprox.(result["solution"]["switch"]["ohline"]["pf"], [6.0, 6.0, 6.0]; atol=1e-1))
@test isapprox(result["objective"], 18.1824; atol=2e-1)
end
end
end
end
1 change: 1 addition & 0 deletions test/test_cases.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ case3_balanced_pv = parse_file("../test/data/opendss/case3_balanced_pv.dss")
case3_unbalanced_1phase_pv = parse_file("../test/data/opendss/case3_unbalanced_1phase-pv.dss")
case3_blanced_basefreq = parse_file("../test/data/opendss/case3_balanced_basefreq.dss")
case3_balanced_battery = parse_file("../test/data/opendss/case3_balanced_battery.dss")
case3_balanced_switch = parse_file("../test/data/opendss/case3_balanced_switch.dss")

# 3-bus unbalanced
case3_unbalanced = parse_file("../test/data/opendss/case3_unbalanced.dss")
Expand Down

0 comments on commit f5c785e

Please sign in to comment.